Repository: UPCBasicEngine/Sobrassada_Engine
Pull request #90: Animation editor THO-146
Author: login: DHazardFenrir
Created: 2025-04-06T22:53:51Z | Merged: 2025-04-11T17:09:10Z

Animation Editor with Skinning.
How to Test:
Create New Scene
Import Player o Zombunny to scene and load the respective model. 
Select the rootGameobject y select the animation Component. There are the controllers for play, pause, etc. If you click play the animation should show, stop should stop it. 

I erased the animation editor since it made the flow of playing, selecting gameobjects kind of cumbersome, so with everything in the component is has more easy access.

Note: There is some errour in the animation resource or the import, i dont know what is the error exactly, but some channels are not importing, causing that there aren't those channels for the bones, so the animations are not quite complete in a way. 

Functions to add since this feature is urgent and I cannot spend more time on it: If you delete the animation component and then you add it again manually, the animation component doesn't automatically search for the animation, so it stays empty.  It has possible performance problem in the getTransform method since it has two while loops (one for rotation and position) when looking through indexes. 



Diff:
diff --git a/SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp b/SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp
new file mode 100644
index 000000000..cf52a883d
--- /dev/null
+++ b/SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp
@@ -0,0 +1,221 @@
+#include "AnimationImporter.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "LibraryModule.h"
+#include "MetaAnimation.h"
+#include "ProjectModule.h"
+#include "ResourceAnimation.h"
+
+#include "rapidjson/document.h"
+#include <memory>
+#include <tiny_gltf.h>
+#include <vector>
+
+namespace AnimationImporter
+{
+    UID ImportAnimation(
+        const tinygltf::Model& model, const tinygltf::Animation& animation, const std::string& name, const char* sourceFilePath,
+        const std::string& targetFilePath, UID sourceUID
+    )
+    {
+        std::vector<char> buffer;
+
+        uint32_t channelCount = static_cast<uint32_t>(animation.channels.size());
+        buffer.insert(
+            buffer.end(), reinterpret_cast<char*>(&channelCount),
+            reinterpret_cast<char*>(&channelCount) + sizeof(uint32_t)
+        );
+
+        for (const auto& channel : animation.channels)
+        {
+            const tinygltf::AnimationSampler& sampler = animation.samplers[channel.sampler];
+
+            const tinygltf::Accessor& input           = model.accessors[sampler.input];
+            const tinygltf::Accessor& output          = model.accessors[sampler.output];
+
+            const tinygltf::BufferView& inputView     = model.bufferViews[input.bufferView];
+            const tinygltf::BufferView& outputView    = model.bufferViews[output.bufferView];
+
+            const tinygltf::Buffer& inputBuffer       = model.buffers[inputView.buffer];
+            const tinygltf::Buffer& outputBuffer      = model.buffers[outputView.buffer];
+
+            const float* timeStamps =
+                reinterpret_cast<const float*>(&inputBuffer.data[input.byteOffset + inputView.byteOffset]);
+            const float* values =
+                reinterpret_cast<const float*>(&outputBuffer.data[output.byteOffset + outputView.byteOffset]);
+
+            const size_t keyframeCount        = input.count;
+
+            const std::string& nodeName = model.nodes[channel.target_node].name;
+            const uint32_t nameSize     = static_cast<uint32_t>(nodeName.size());
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&nameSize),
+                reinterpret_cast<const char*>(&nameSize) + sizeof(uint32_t)
+            );
+            buffer.insert(buffer.end(), nodeName.begin(), nodeName.end());
+
+            AnimationType animType = AnimationType::TRANSLATION;
+
+            if (channel.target_path == "rotation")
+            {
+                animType = AnimationType::ROTATION;
+            }
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&animType),
+                reinterpret_cast<const char*>(&animType) + sizeof(AnimationType)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&keyframeCount),
+                reinterpret_cast<const char*>(&keyframeCount) + sizeof(uint32_t)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(timeStamps),
+                reinterpret_cast<const char*>(timeStamps) + keyframeCount * sizeof(float)
+            );
+
+            if (animType == AnimationType::TRANSLATION)
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(float3)
+                );
+            }
+            else
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(Quat)
+                );
+            }
+        }
+
+        // Handle UID
+        const std::string fileName = FileSystem::GetFileNameWithoutExtension(sourceFilePath);
+        UID finalAnimUID;
+        if (sourceUID == INVALID_UID)
+        {
+            const UID animationUID      = GenerateUID();
+            finalAnimUID          = App->GetLibraryModule()->AssignFiletypeUID(animationUID, FileType::Animation);
+
+            const std::string assetPath = ANIMATIONS_PATH + FileSystem::GetFileNameWithExtension(sourceFilePath);
+            MetaAnimation meta(finalAnimUID, assetPath);
+
+            
+            
+            meta.Save(fileName, assetPath);
+        }
+        else
+        {
+            finalAnimUID = sourceUID;
+        }
+
+        // Construct save paths 
+
+        const std::string saveFilePath = App->GetProjectModule()->GetLoadedProjectPath() + ANIMATIONS_PATH +
+                                   std::to_string(finalAnimUID) + ANIMATION_EXTENSION;
+      
+        const size_t bytesWritten =
+            FileSystem::Save(saveFilePath.c_str(), buffer.data(), static_cast<unsigned int>(buffer.size()), true);
+
+        if (bytesWritten == 0)
+        {
+            GLOG("Failed to save animation file: %s", saveFilePath.c_str());
+            return 0;
+        }
+
+      
+        App->GetLibraryModule()->AddAnimation(finalAnimUID, fileName);
+        App->GetLibraryModule()->AddName(fileName, finalAnimUID);
+        App->GetLibraryModule()->AddResource(saveFilePath, finalAnimUID);
+
+        GLOG("%s saved as binary", FileSystem::GetFileNameWithoutExtension(sourceFilePath).c_str());
+
+        return finalAnimUID;
+    }
+
+    ResourceAnimation* LoadAnimation(UID animationUID)
+    {
+        const std::string path      = App->GetLibraryModule()->GetResourcePath(animationUID);
+        char* buffer                = nullptr;
+        const unsigned int fileSize = FileSystem::Load(path.c_str(), &buffer);
+
+        if (fileSize == 0 || buffer == nullptr)
+        {
+            GLOG("Failed to load animation file: %s", path.c_str());
+            return nullptr;
+        }
+
+        char* cursor = buffer;
+
+        uint32_t channelCount;
+        memcpy(&channelCount, cursor, sizeof(uint32_t));
+        cursor += sizeof(uint32_t);
+
+        // Create animation resource
+        ResourceAnimation* animation =
+            new ResourceAnimation(animationUID, FileSystem::GetFileNameWithoutExtension(path));
+
+        // Parse channels
+        for (uint32_t i = 0; i < channelCount; ++i)
+        {
+
+            uint32_t nameSize;
+            memcpy(&nameSize, cursor, sizeof(uint32_t));
+            cursor += sizeof(uint32_t);
+
+            std::string nodeName(cursor, nameSize);
+            cursor += nameSize;
+
+            AnimationType animType;
+            memcpy(&animType, cursor, sizeof(AnimationType));
+            cursor += sizeof(AnimationType);
+
+            uint32_t keyframeCount;
+            memcpy(&keyframeCount, cursor, sizeof(uint32_t));
+            cursor               += sizeof(uint32_t);
+
+            // Get channel reference
+            Channel& animChannel  = animation->channels[nodeName];
+
+            // Parse based on animation type
+            if (animType == AnimationType::TRANSLATION)
+            {
+
+                animChannel.posTimeStamps.resize(keyframeCount);
+                memcpy(animChannel.posTimeStamps.data(), cursor, keyframeCount * sizeof(float));
+                cursor += keyframeCount * sizeof(float);
+
+                animChannel.positions.resize(keyframeCount);
+                memcpy(animChannel.positions.data(), cursor, keyframeCount * sizeof(float3));
+                cursor                   += keyframeCount * sizeof(float3);
+
+                animChannel.numPositions  = keyframeCount;
+            }
+            else
+            {
+                animChannel.rotTimeStamps.resize(keyframeCount);
+                memcpy(animChannel.rotTimeStamps.data(), cursor, keyframeCount * sizeof(float));
+                cursor += keyframeCount * sizeof(float);
+
+                animChannel.rotations.resize(keyframeCount);
+                memcpy(animChannel.rotations.data(), cursor, keyframeCount * sizeof(Quat));
+                cursor                   += keyframeCount * sizeof(Quat);
+
+                animChannel.numRotations  = keyframeCount;
+            }
+        }
+
+        delete[] buffer;
+
+        animation->SetDuration();
+
+        GLOG("Animation duration: %f", animation->GetDuration());
+
+        return animation;
+    }
+} // namespace AnimationImporter
\ No newline at end of file
diff --git a/SobrassadaEngine/FileSystem/Importers/AnimationImporter.h b/SobrassadaEngine/FileSystem/Importers/AnimationImporter.h
new file mode 100644
index 000000000..49db0cc1c
--- /dev/null
+++ b/SobrassadaEngine/FileSystem/Importers/AnimationImporter.h
@@ -0,0 +1,30 @@
+#pragma once
+
+#include "Globals.h"
+
+
+
+namespace tinygltf
+{
+    class Model;
+    struct Animation;
+    struct AnimationSampler;
+    struct Accessor;
+    struct BufferView;
+    struct Buffer;
+} // namespace tinygltf
+class ResourceAnimation;
+
+enum class  AnimationType : uint8_t
+{   TRANSLATION,
+    ROTATION
+};
+namespace AnimationImporter
+{
+    UID ImportAnimation(
+        const tinygltf::Model& model, const tinygltf::Animation& animation, const std::string& name,
+        const char* sourceFilePath, const std::string& targetFilePath, UID sourceUID
+    );
+    ResourceAnimation* LoadAnimation(UID animationUID);
+   
+}; 
\ No newline at end of file
diff --git a/SobrassadaEngine/FileSystem/Importers/Importer.cpp b/SobrassadaEngine/FileSystem/Importers/Importer.cpp
index 8d88673f6..6d730ffca 100644
--- a/SobrassadaEngine/FileSystem/Importers/Importer.cpp
+++ b/SobrassadaEngine/FileSystem/Importers/Importer.cpp
@@ -5,6 +5,7 @@
 #include "MaterialImporter.h"
 #include "MeshImporter.h"
 #include "ModelImporter.h"
+#include "AnimationImporter.h"
 #include "PrefabManager.h"
 #include "FontImporter.h"
 #include "StateMachineManager.h"
@@ -17,6 +18,7 @@
 #include "ResourceFont.h"
 #include "ResourceStateMachine.h"
 #include "TextureImporter.h"
+#include "ResourceAnimation.h"
 
 Resource* Importer::Load(UID uid)
 {
@@ -34,6 +36,8 @@ Resource* Importer::Load(UID uid)
         return MeshImporter::LoadMesh(uid);
     case ResourceType::Model:
         return ModelImporter::LoadModel(uid);
+    case ResourceType::Animation:
+    return AnimationImporter::LoadAnimation(uid);
     case ResourceType::Prefab:
         return PrefabManager::LoadPrefab(uid);
     case ResourceType::StateMachine:
diff --git a/SobrassadaEngine/FileSystem/Importers/ModelImporter.cpp b/SobrassadaEngine/FileSystem/Importers/ModelImporter.cpp
index 6f4c2f1cd..298df65c8 100644
--- a/SobrassadaEngine/FileSystem/Importers/ModelImporter.cpp
+++ b/SobrassadaEngine/FileSystem/Importers/ModelImporter.cpp
@@ -8,7 +8,7 @@
 #include "Model.h"
 #include "ProjectModule.h"
 #include "ResourceModel.h"
-
+#include "AnimationImporter.h"
 #include "Math/Quat.h"
 #include "Math/float4x4.h"
 #include "rapidjson/prettywriter.h"
@@ -96,6 +96,51 @@ namespace ModelImporter
         else finalModelUID = sourceUID;
 
         assetPath = targetFilePath + assetPath;
+        std::vector<UID> animationUIDs;
+        // Import Animations
+        if (model.animations.size() > 0)
+        {
+
+            GLOG("Number of animations in model: %zu", model.animations.size());
+
+            for (int i = 0; i < model.animations.size(); ++i)
+            {
+                const auto& anim = model.animations[i];
+
+                GLOG("Importing animation %d", i);
+                GLOG("Animation channels: %zu", anim.channels.size());
+
+                UID animUID =
+                    AnimationImporter::ImportAnimation(model, anim, anim.name, filePath, targetFilePath, sourceUID);
+
+                GLOG("Imported animation UID: %llu", animUID);
+
+                if (animUID != INVALID_UID)
+                {
+                    animationUIDs.push_back(animUID);
+
+                    GLOG("Final animation UID: %llu", animUID);
+                }
+
+                else
+                {
+                    GLOG("Failed to import animation %d", i);
+                }
+            }
+
+            if (!animationUIDs.empty())
+            {
+                newModel.SetAnimationUID(animationUIDs[0]);
+                newModel.SetAllAnimationUIDs(animationUIDs);
+
+                GLOG("Set first animation UID: %llu", animationUIDs[0]);
+                GLOG("Total animation UIDs: %zu", animationUIDs.size());
+            }
+            else
+            {
+                GLOG("No valid animation UIDs found");
+            }
+        }
 
         newModel.SetUID(finalModelUID);
 
@@ -155,7 +200,13 @@ namespace ModelImporter
         }
 
         modelJSON.AddMember("Nodes", nodesJSON, allocator);
-
+        // Serialize Animations.
+        rapidjson::Value animationsJSON(rapidjson::kArrayType);
+        for (UID animUID : animationUIDs)
+        {
+            animationsJSON.PushBack(animUID, allocator);
+        }
+        modelJSON.AddMember("Animations", animationsJSON, allocator);
         // Serialize skins
         rapidjson::Value skinsJSON(rapidjson::kArrayType);
         for (const Skin& skin : skinsData)
@@ -191,7 +242,7 @@ namespace ModelImporter
         modelJSON.AddMember("Skins", skinsJSON, allocator);
 
         doc.AddMember("Model", modelJSON, allocator);
-
+       
         // Save file like JSON
         rapidjson::StringBuffer buffer;
         rapidjson::PrettyWriter<rapidjson::StringBuffer> writer(buffer);
@@ -328,6 +379,21 @@ namespace ModelImporter
             }
         }
 
+         std::vector<UID> loadedAnimations;
+        // Deserialize Animations
+        if (modelJSON.HasMember("Animations") && modelJSON["Animations"].IsArray())
+        {
+
+            const rapidjson::Value& animJSON = modelJSON["Animations"];
+
+            for (rapidjson::SizeType i = 0; i < animJSON.Size(); ++i)
+            {
+
+                loadedAnimations.push_back(animJSON[i].GetUint64());
+            }
+        }
+
+
         // Deserialize Skins
         std::vector<Skin> loadedSkins;
         if (modelJSON.HasMember("Skins") && modelJSON["Skins"].IsArray())
@@ -382,6 +448,11 @@ namespace ModelImporter
 
         ResourceModel* resourceModel = new ResourceModel(uid, FileSystem::GetFileNameWithoutExtension(filePath));
         resourceModel->SetModelData(Model(uid, rootNodesIdx, loadedNodes, loadedSkins));
+        if (loadedAnimations.size() > 0)
+        {
+            resourceModel->SetAllAnimationUIDs(loadedAnimations);
+            resourceModel->SetAnimationUID(loadedAnimations[0]);
+        }
 
         return resourceModel;
     }
@@ -415,7 +486,7 @@ namespace ModelImporter
             // Get reference to Mesh and Material UIDs
             if (nodeData.mesh > -1) newNode.meshes = meshesUIDs[nodeData.mesh];
 
-            newNode.skinIndex = nodeData.skin;
+            newNode.skinIndex          = nodeData.skin;
 
             outNodes[currentNodeIndex] = newNode;
 
@@ -425,4 +496,4 @@ namespace ModelImporter
             }
         }
     }
-} // namespace ModelImporter
+} // namespace ModelImporter
\ No newline at end of file
diff --git a/SobrassadaEngine/FileSystem/Importers/ModelImporter.h b/SobrassadaEngine/FileSystem/Importers/ModelImporter.h
index c515f901b..1c831c13a 100644
--- a/SobrassadaEngine/FileSystem/Importers/ModelImporter.h
+++ b/SobrassadaEngine/FileSystem/Importers/ModelImporter.h
@@ -34,4 +34,4 @@ namespace ModelImporter
         const std::vector<tinygltf::Node>& nodesList, int nodeId,
         const std::vector<std::vector<std::pair<UID, UID>>>& meshesUIDs, std::vector<NodeData>& outNodes
     );
-}; // namespace ModelImporter
+}; // namespace ModelImporter
\ No newline at end of file
diff --git a/SobrassadaEngine/FileSystem/Importers/SceneImporter.cpp b/SobrassadaEngine/FileSystem/Importers/SceneImporter.cpp
index f5fea616d..cb37f6122 100644
--- a/SobrassadaEngine/FileSystem/Importers/SceneImporter.cpp
+++ b/SobrassadaEngine/FileSystem/Importers/SceneImporter.cpp
@@ -4,6 +4,7 @@
 #include "FileSystem.h"
 #include "FontImporter.h"
 #include "MaterialImporter.h"
+#include "AnimationImporter.h"
 #include "MeshImporter.h"
 #include "ModelImporter.h"
 #include "PrefabManager.h"
@@ -170,6 +171,23 @@ namespace SceneImporter
         }
     }
 
+    void ImportAnimationFromMetadata(
+        const std::string& filePath, const std::string& targetFilePath, const std::string& name, UID sourceUID
+    )
+    {
+        tinygltf::Model model = LoadModelGLTF(filePath.c_str(), targetFilePath);
+
+        // find material name that equals to name
+        for (int i = 0; i < model.animations.size(); i++)
+        {
+            if (model.animations[i].name == name)
+            {
+               AnimationImporter::ImportAnimation(model, model.animations[i], name, filePath.c_str(), targetFilePath, sourceUID);
+                return; // only one animation with the same name 
+            }
+        }
+    }
+
     void
     CopyPrefab(const std::string& filePath, const std::string& targetFilePath, const std::string& name, UID sourceUID)
     {
@@ -181,6 +199,7 @@ namespace SceneImporter
     {
         ModelImporter::CopyModel(filePath, targetFilePath, name, sourceUID);
     }
+ 
 
     void
     CopyFont(const std::string& filePath, const std::string& targetFilePath, const std::string& name, UID sourceUID)
@@ -188,6 +207,7 @@ namespace SceneImporter
         FontImporter::CopyFont(filePath, targetFilePath, name, sourceUID);
     }
 
+
     void CreateLibraryDirectories(const std::string& projectFilePath)
     {
         const std::string convertedAssetPath = projectFilePath + ASSETS_PATH;
diff --git a/SobrassadaEngine/FileSystem/Importers/SceneImporter.h b/SobrassadaEngine/FileSystem/Importers/SceneImporter.h
index 797ddad3e..1fd183e51 100644
--- a/SobrassadaEngine/FileSystem/Importers/SceneImporter.h
+++ b/SobrassadaEngine/FileSystem/Importers/SceneImporter.h
@@ -18,6 +18,9 @@ namespace SceneImporter
     void ImportMaterialFromMetadata(
         const std::string& filePath, const std::string& targetFilePath, const std::string& name, UID sourceUID
     );
+    void ImportAnimationFromMetadata(
+        const std::string& filePath, const std::string& targetFilePath, const std::string& name, UID sourceUID
+    );
     void
     CopyPrefab(const std::string& filePath, const std::string& targetFilePath, const std::string& name, UID sourceUID);
     void
diff --git a/SobrassadaEngine/FileSystem/Meta/MetaAnimation.cpp b/SobrassadaEngine/FileSystem/Meta/MetaAnimation.cpp
new file mode 100644
index 000000000..f405c8946
--- /dev/null
+++ b/SobrassadaEngine/FileSystem/Meta/MetaAnimation.cpp
@@ -0,0 +1,10 @@
+#include "MetaAnimation.h"
+MetaAnimation::MetaAnimation(UID uid, const std::string& assetPath) : MetaFile(uid, assetPath)
+{
+}
+
+
+void MetaAnimation::AddImportOptions(rapidjson::Document& doc, rapidjson::Document::AllocatorType& allocator) const
+{
+    
+}
diff --git a/SobrassadaEngine/FileSystem/Meta/MetaAnimation.h b/SobrassadaEngine/FileSystem/Meta/MetaAnimation.h
new file mode 100644
index 000000000..18a73ba40
--- /dev/null
+++ b/SobrassadaEngine/FileSystem/Meta/MetaAnimation.h
@@ -0,0 +1,12 @@
+#pragma once
+
+#include "MetaFile.h"
+
+class MetaAnimation : public MetaFile
+{
+  public:
+	MetaAnimation(UID uid, const std::string& assetPath);
+    void AddImportOptions(rapidjson::Document& doc, rapidjson::Document::AllocatorType& allocator) const override;
+
+	
+};
diff --git a/SobrassadaEngine/FileSystem/Model.h b/SobrassadaEngine/FileSystem/Model.h
index b8cf4b2f3..4d281df50 100644
--- a/SobrassadaEngine/FileSystem/Model.h
+++ b/SobrassadaEngine/FileSystem/Model.h
@@ -41,10 +41,13 @@ class Model
     void SetRootNodesIdx(const std::vector<int>& rootNodes) { this->rootNodesIdx = rootNodes; }
     void SetNodes(const std::vector<NodeData>& nodes) { this->nodes = nodes; }
     void SetSkins(const std::vector<Skin>& skins) { this->skins = skins; }
-
+    void SetAnimationUID(UID animationUID) { animUID = animationUID; }
+    void SetAllAnimationUIDs(std::vector<UID> animUids) { allAnimUIDs = animUids; }
   private:
     UID uid;
     std::vector<int> rootNodesIdx;
     std::vector<NodeData> nodes;
     std::vector<Skin> skins;
+    UID animUID = INVALID_UID;
+    std::vector<UID> allAnimUIDs;
 };
\ No newline at end of file
diff --git a/SobrassadaEngine/Modules/EditorUIModule.cpp b/SobrassadaEngine/Modules/EditorUIModule.cpp
index 4953a620e..a4115b6ba 100644
--- a/SobrassadaEngine/Modules/EditorUIModule.cpp
+++ b/SobrassadaEngine/Modules/EditorUIModule.cpp
@@ -43,6 +43,7 @@ EditorUIModule::EditorUIModule() : width(0), height(0)
         {"Spot Light",           COMPONENT_SPOT_LIGHT          },
         {"Directional Light",    COMPONENT_DIRECTIONAL_LIGHT   },
         {"Character Controller", COMPONENT_CHARACTER_CONTROLLER},
+        {"Animation",            COMPONENT_ANIMATION           },
         {"Transform 2D",         COMPONENT_TRANSFORM_2D        },
         {"UI Canvas",            COMPONENT_CANVAS              },
         {"UI Label",             COMPONENT_LABEL               },
@@ -330,7 +331,7 @@ void EditorUIModule::MainMenu()
         {
             if (ImGui::MenuItem("Mockup Base Engine Editor", "")) OpenEditor(CreateEditor(EditorType::BASE));
 
-            if (ImGui::MenuItem("Node Editor Engine Editor", "")) OpenEditor(CreateEditor(EditorType::NODE));
+            if (ImGui::MenuItem("Node Editor", "")) OpenEditor(CreateEditor(EditorType::NODE));
 
             if (ImGui::MenuItem("State Machine Editor Engine Editor", "")) OpenEditor(CreateEditor(EditorType::ANIMATION));
             if (ImGui::MenuItem("Texture Editor Engine Editor", "")) OpenEditor(CreateEditor(EditorType::TEXTURE));
diff --git a/SobrassadaEngine/Modules/EditorUIModule.h b/SobrassadaEngine/Modules/EditorUIModule.h
index 40cc7d77e..79adf5b11 100644
--- a/SobrassadaEngine/Modules/EditorUIModule.h
+++ b/SobrassadaEngine/Modules/EditorUIModule.h
@@ -25,9 +25,9 @@ namespace ImGuizmo
 enum EditorType
 {
     BASE,
-    ANIMATION,
     NODE,
-    TEXTURE
+    TEXTURE, 
+    ANIMATION
 };
 
 enum class GizmoOperation
diff --git a/SobrassadaEngine/Modules/LibraryModule.cpp b/SobrassadaEngine/Modules/LibraryModule.cpp
index f7d45fdb2..7a58d7bb6 100644
--- a/SobrassadaEngine/Modules/LibraryModule.cpp
+++ b/SobrassadaEngine/Modules/LibraryModule.cpp
@@ -172,6 +172,14 @@ bool LibraryModule::LoadLibraryMaps(const std::string& projectPath)
                 if (FileSystem::Exists(libraryPath.c_str())) AddResource(libraryPath, assetUID);
                 else SceneImporter::CopyModel(assetPath, projectPath, assetName, assetUID);
                 break;
+            case 15:
+                AddAnimation(assetUID, assetName);
+                AddName(assetName, assetUID);
+                libraryPath = projectPath + ANIMATIONS_PATH+ std::to_string(assetUID) + ANIMATION_EXTENSION;
+                if (FileSystem::Exists(libraryPath.c_str())) AddResource(libraryPath, assetUID);
+                else SceneImporter::CopyModel(assetPath, projectPath, assetName, assetUID);
+                break;
+
             case 16:
                 AddPrefab(assetUID, assetName);
                 AddName(assetName, assetUID);
@@ -254,6 +262,9 @@ UID LibraryModule::AssignFiletypeUID(UID originalUID, FileType fileType)
     case FileType::Model:
         prefix = 14;
         break;
+    case FileType::Animation:
+        prefix = 15;
+        break;
     case FileType::Prefab:
         prefix = 16;
         break;
@@ -294,6 +305,11 @@ void LibraryModule::AddModel(UID modelUID, const std::string& modelName)
     modelMap[modelName] = modelUID;
 }
 
+void LibraryModule::AddAnimation(UID animUID, const std::string& animName)
+{
+    animMap[animName] = animUID;
+}
+
 void LibraryModule::AddPrefab(UID prefabUID, const std::string& prefabName)
 {
     prefabMap[prefabName] = prefabUID;
@@ -358,7 +374,20 @@ UID LibraryModule::GetModelUID(const std::string& modelPath) const
     return INVALID_UID;
 }
 
+
+UID LibraryModule::GetAnimUID(const std::string& animPath) const
+{
+    auto it = animMap.find(animPath);
+    if (it != animMap.end())
+    {
+        return it->second;
+    }
+
+    return INVALID_UID;
+}
+
 UID LibraryModule::GetStateMachineUID(const std::string& stMachPath) const
+
 {
     auto it = stateMachineMap.find(stMachPath);
     if (it != stateMachineMap.end())
diff --git a/SobrassadaEngine/Modules/LibraryModule.h b/SobrassadaEngine/Modules/LibraryModule.h
index c4a3b7a1c..ce670b030 100644
--- a/SobrassadaEngine/Modules/LibraryModule.h
+++ b/SobrassadaEngine/Modules/LibraryModule.h
@@ -21,6 +21,7 @@ enum class FileType
     Material,
     Scene,
     Model,
+    Animation,
     Prefab,
     StateMachine,
     Font
@@ -50,6 +51,7 @@ class LibraryModule : public Module
     void AddMaterial(UID materialUID, const std::string& sobPath);
     void AddPrefab(UID prefabUID, const std::string& prefabPath);
     void AddModel(UID modelUID, const std::string& modelPath);
+    void AddAnimation(UID animUID, const std::string& animPath);
     void AddStateMachine(UID stateMachineUID, const std::string& stMachPath);
     void AddFont(UID fontUID, const std::string& fontName);
     void AddName(const std::string& resourceName, UID resourceUID);
@@ -59,8 +61,10 @@ class LibraryModule : public Module
     UID GetMeshUID(const std::string& meshPath) const;
     UID GetMaterialUID(const std::string& materialPath) const;
     UID GetModelUID(const std::string& modelPath) const;
+    UID GetAnimUID(const std::string& animPath) const;
     UID GetStateMachineUID(const std::string& stMachPath) const;
 
+
     const std::string& GetResourceName(UID resourceID) const;
 
     const std::string& GetResourcePath(UID resourceID) const;
@@ -69,6 +73,7 @@ class LibraryModule : public Module
     const std::unordered_map<std::string, UID>& GetMaterialMap() const { return materialMap; }
     const std::unordered_map<std::string, UID>& GetMeshMap() const { return meshMap; }
     const std::unordered_map<std::string, UID>& GetModelMap() const { return modelMap; }
+    const std::unordered_map<std::string, UID>& GetAnimMap() const { return animMap; }
     const std::unordered_map<std::string, UID>& GetPrefabMap() const { return prefabMap; }
     const std::unordered_map<std::string, UID>& GetStateMachinePath() const { return stateMachineMap; }
     const std::unordered_map<std::string, UID>& GetFontMap() const { return fontMap; }
@@ -81,7 +86,10 @@ class LibraryModule : public Module
     std::unordered_map<std::string, UID> prefabMap;
     std::unordered_map<std::string, UID> modelMap;
     std::unordered_map<std::string, UID> stateMachineMap;
-    std::unordered_map<std::string, UID> fontMap;
+
+    std::unordered_map<std::string, UID> animMap;
+     std::unordered_map<std::string, UID> fontMap;
+
     // inversed map          | UID -> name
     std::unordered_map<UID, std::string> namesMap;
 
diff --git a/SobrassadaEngine/ProjectConfig.json b/SobrassadaEngine/ProjectConfig.json
new file mode 100644
index 000000000..0aa223799
--- /dev/null
+++ b/SobrassadaEngine/ProjectConfig.json
@@ -0,0 +1,3 @@
+{
+    "StartupScenePath": ""
+}
\ No newline at end of file
diff --git a/SobrassadaEngine/ResourceManagement/Resources/ResourceAnimation.cpp b/SobrassadaEngine/ResourceManagement/Resources/ResourceAnimation.cpp
new file mode 100644
index 000000000..b78f7f263
--- /dev/null
+++ b/SobrassadaEngine/ResourceManagement/Resources/ResourceAnimation.cpp
@@ -0,0 +1,57 @@
+#include "ResourceAnimation.h"
+
+#include "Application.h"
+#include "LibraryModule.h"
+#include "TextureImporter.h"
+
+#include <glew.h>
+#include <unordered_set>
+
+#include "imgui.h"
+#include "DirectXTex/DirectXTex.h"
+#define TINYGLTF_NO_STB_IMAGE_WRITE
+#define TINYGLTF_NO_STB_IMAGE
+#define TINYGLTF_NO_EXTERNAL_IMAGE
+#include <tiny_gltf.h>
+
+ResourceAnimation::ResourceAnimation(UID uid, const std::string& name) : Resource(uid, name, ResourceType::Animation)
+{
+}
+
+ResourceAnimation::~ResourceAnimation()
+{
+   
+}
+
+
+void ResourceAnimation::SetDuration()
+{
+    float maxTime = 0.0f;
+
+    for (const auto& channel : channels)
+    {
+        if (channel.second.numPositions > 0)
+        {
+            float lastTime = channel.second.posTimeStamps[channel.second.numPositions - 1];
+            maxTime        = std::max(maxTime, lastTime);
+        }
+        if (channel.second.numRotations > 0)
+        {
+            float lastTime = channel.second.rotTimeStamps[channel.second.numRotations - 1];
+            maxTime        = std::max(maxTime, lastTime);
+        }
+    }
+
+    duration = maxTime;
+}
+
+Channel* ResourceAnimation::GetChannel(const std::string& nodeName)
+{
+    auto it = channels.find(nodeName);
+    if (it != channels.end())
+    {
+        return &it->second;
+    }
+
+    return nullptr; 
+}
\ No newline at end of file
diff --git a/SobrassadaEngine/ResourceManagement/Resources/ResourceAnimation.h b/SobrassadaEngine/ResourceManagement/Resources/ResourceAnimation.h
new file mode 100644
index 000000000..d92916bad
--- /dev/null
+++ b/SobrassadaEngine/ResourceManagement/Resources/ResourceAnimation.h
@@ -0,0 +1,34 @@
+#pragma once
+#include "Resource.h"
+#include "Math/float3.h"
+#include "Math/Quat.h"
+#include <unordered_map>
+
+struct Channel
+{
+    std::vector<float3> positions;
+    std::vector<float> posTimeStamps;
+    std::vector<Quat> rotations;
+    std::vector<float> rotTimeStamps;
+    uint32_t numPositions = 0;
+    uint32_t numRotations = 0;
+};
+
+class ResourceAnimation : public Resource
+{
+  public:
+    ResourceAnimation(UID uid, const std::string& name);
+    ~ResourceAnimation() override;
+
+    void SetDuration();
+    float GetDuration() { return duration; }
+    Channel* GetChannel(const std::string& nodeName);
+
+  public:
+    std::unordered_map<std::string, Channel> channels;
+    
+  private:
+    
+    float duration = 0.0f;
+
+};
diff --git a/SobrassadaEngine/ResourceManagement/Resources/ResourceModel.h b/SobrassadaEngine/ResourceManagement/Resources/ResourceModel.h
index 5626daba0..4869a07df 100644
--- a/SobrassadaEngine/ResourceManagement/Resources/ResourceModel.h
+++ b/SobrassadaEngine/ResourceManagement/Resources/ResourceModel.h
@@ -8,11 +8,15 @@ class ResourceModel : public Resource
   public:
     ResourceModel(UID uid, const std::string& name);
     ~ResourceModel() override;
-
+    UID GetAnimationUID() const { return animUID; }
     const Model& GetModelData() const { return modelData; }
-
+    void SetAnimationUID(UID animationUID) { animUID = animationUID; }
+    const std::vector<UID>& GetAllAnimationUIDs() const { return allAnimUIDs; }
+    void SetAllAnimationUIDs(std::vector<UID> animUids) { allAnimUIDs = animUids;}
     void SetModelData(const Model&& model) { modelData = model; }
 
   private:
     Model modelData;
+    UID animUID = INVALID_UID;
+    std::vector<UID> allAnimUIDs;
 };
\ No newline at end of file
diff --git a/SobrassadaEngine/Scene/Components/ComponentUtils.cpp b/SobrassadaEngine/Scene/Components/ComponentUtils.cpp
index fbb16abc0..e781bf4aa 100644
--- a/SobrassadaEngine/Scene/Components/ComponentUtils.cpp
+++ b/SobrassadaEngine/Scene/Components/ComponentUtils.cpp
@@ -1,20 +1,20 @@
 ﻿#include "ComponentUtils.h"
-#include "ComponentUtils.h"
 
 #include "CameraComponent.h"
 #include "Component.h"
+#include "ScriptComponent.h"
+#include "Standalone/AnimationComponent.h"
 #include "Standalone/CharacterControllerComponent.h"
 #include "Standalone/Lights/DirectionalLightComponent.h"
 #include "Standalone/Lights/PointLightComponent.h"
 #include "Standalone/Lights/SpotLightComponent.h"
 #include "Standalone/MeshComponent.h"
+#include "Standalone/Physics/CapsuleColliderComponent.h"
+#include "Standalone/Physics/CubeColliderComponent.h"
+#include "Standalone/Physics/SphereColliderComponent.h"
 #include "Standalone/UI/CanvasComponent.h"
 #include "Standalone/UI/Transform2DComponent.h"
 #include "Standalone/UI/UILabelComponent.h"
-#include "Standalone/Physics/CubeColliderComponent.h"
-#include "Standalone/Physics/SphereColliderComponent.h"
-#include "Standalone/Physics/CapsuleColliderComponent.h"
-#include "ScriptComponent.h"
 
 #include <cstdint>
 
@@ -64,6 +64,9 @@ Component* ComponentUtils::CreateEmptyComponent(const ComponentType type, const
     case COMPONENT_CAPSULE_COLLIDER:
         generatedComponent = new CapsuleColliderComponent(uid, parent);
         break;
+    case COMPONENT_ANIMATION:
+        generatedComponent = new AnimationComponent(uid, parent);
+        break;
     default:
         return nullptr;
     }
@@ -107,6 +110,8 @@ Component* ComponentUtils::CreateExistingComponent(const rapidjson::Value& initi
             return new SphereColliderComponent(initialState, parent);
         case COMPONENT_CAPSULE_COLLIDER:
             return new CapsuleColliderComponent(initialState, parent);
+        case COMPONENT_ANIMATION:
+            return new AnimationComponent(initialState, parent);
         default:
             return nullptr;
         }
diff --git a/SobrassadaEngine/Scene/Components/ComponentUtils.h b/SobrassadaEngine/Scene/Components/ComponentUtils.h
index cd81a4a66..4a61395ca 100644
--- a/SobrassadaEngine/Scene/Components/ComponentUtils.h
+++ b/SobrassadaEngine/Scene/Components/ComponentUtils.h
@@ -28,8 +28,9 @@ enum ComponentType
     COMPONENT_CUBE_COLLIDER,
     COMPONENT_SPHERE_COLLIDER,
     COMPONENT_CAPSULE_COLLIDER,
+    COMPONENT_ANIMATION,
     FIRST = COMPONENT_NONE,
-    LAST  = COMPONENT_CAPSULE_COLLIDER
+    LAST  = COMPONENT_ANIMATION,
 };
 
 enum class ColliderType : uint8_t
@@ -38,6 +39,7 @@ enum class ColliderType : uint8_t
     DYNAMIC,
     KINEMATIC,
     TRIGGER
+
 };
 
 constexpr const char* ColliderTypeStrings[] = {"Static", "Dynamic", "Kinematic", "Trigger"};
diff --git a/SobrassadaEngine/Scene/Components/Standalone/AnimController.cpp b/SobrassadaEngine/Scene/Components/Standalone/AnimController.cpp
new file mode 100644
index 000000000..9a702636d
--- /dev/null
+++ b/SobrassadaEngine/Scene/Components/Standalone/AnimController.cpp
@@ -0,0 +1,190 @@
+#include "AnimController.h"
+
+#include "Application.h"
+#include "EngineTimer.h"
+#include "ResourceAnimation.h"
+#include "ResourcesModule.h"
+
+AnimController::AnimController()
+    : resource(0), currentTime(0), loop(false), playAnimation(false), playbackSpeed(1.0f), animation(nullptr)
+{
+}
+
+AnimController::~AnimController()
+{
+    App->GetResourcesModule()->ReleaseResource(animation);
+}
+
+void AnimController::Play(UID newResource, bool shouldLoop)
+{
+    resource      = newResource;
+    currentTime   = 0;
+    loop          = shouldLoop;
+    animation     = static_cast<ResourceAnimation*>(App->GetResourcesModule()->RequestResource(resource));
+    playAnimation = true;
+}
+
+void AnimController::Stop()
+{
+    playAnimation = false;
+    currentTime   = 0.0f;
+
+    if (animation != nullptr)
+    {
+        App->GetResourcesModule()->ReleaseResource(animation);
+        animation = nullptr;
+    }
+}
+
+Quat AnimController::Interpolate(Quat& first, Quat& second, float lambda)
+{
+    if (first.Dot(second) >= 0.0f) return Quat::Lerp(first, second, lambda).Normalized();
+    else return Quat::Lerp(first, second.Neg(), lambda).Normalized();
+}
+
+update_status AnimController::Update(float deltaTime)
+{
+    if (!playAnimation || resource == 0) return UPDATE_CONTINUE;
+
+    deltaTime          *= playbackSpeed;
+
+    /*GLOG(
+        "Raw delta time: %f, Capped delta: %f, Playback speed: %f", App->GetEngineTimer()->GetTime(), rawDeltaTime,
+        playbackSpeed
+    );*/
+
+    float previousTime  = currentTime;
+    currentTime        += deltaTime;
+
+    if (animation == nullptr) return UPDATE_CONTINUE;
+
+    const float duration = animation->GetDuration();
+    // GLOG("Animation time update: %f -> %f (duration: %f)", previousTime, currentTime, duration);
+
+    if (currentTime > duration)
+    {
+        if (loop)
+        {
+            currentTime = fmod(currentTime, duration);
+            // GLOG("Animation looped: new time = %f", currentTime);
+        }
+        else
+        {
+            currentTime   = duration;
+            // GLOG("Animation reached end: time = %f", currentTime);
+            playAnimation = false;
+
+            if (animation != nullptr)
+            {
+                App->GetResourcesModule()->ReleaseResource(animation);
+                animation = nullptr;
+            }
+        }
+    }
+
+    return UPDATE_CONTINUE;
+}
+
+void AnimController::GetTransform(const std::string& nodeName, float3& pos, Quat& rot)
+{
+    if (!playAnimation || resource == 0 || animation == nullptr) return;
+
+    Channel* animChannel = animation->GetChannel(nodeName);
+    if (animChannel == nullptr) return;
+
+    float animDuration = animation->GetDuration();
+    // GLOG("Animation duration: %f, Current time: %f", animDuration, currentTime);
+
+    if (animChannel->numPositions > 0)
+    {
+        if (animChannel->numPositions == 1)
+        {
+            pos = animChannel->positions[0];
+            // GLOG("Single position keyframe: (%f,%f,%f)", pos.x, pos.y, pos.z);
+        }
+        else
+        {
+            size_t nextIndex = 0;
+            while (nextIndex < animChannel->numPositions && animChannel->posTimeStamps[nextIndex] <= currentTime)
+            {
+                nextIndex++;
+            }
+
+            size_t prevIndex = (nextIndex > 0) ? nextIndex - 1 : 0;
+
+            if (nextIndex >= animChannel->numPositions)
+            {
+                pos = animChannel->positions[animChannel->numPositions - 1];
+                // GLOG("Past last position keyframe: (%f,%f,%f)", pos.x, pos.y, pos.z);
+            }
+            else if (nextIndex == 0)
+            {
+                pos = animChannel->positions[0];
+                // GLOG("Before first position keyframe: (%f,%f,%f)", pos.x, pos.y, pos.z);
+            }
+            else
+            {
+                const float startTime = animChannel->posTimeStamps[prevIndex];
+                const float endTime   = animChannel->posTimeStamps[nextIndex];
+                const float timeDiff  = endTime - startTime;
+
+                float lambda          = (timeDiff > 0.0001f) ? (currentTime - startTime) / timeDiff : 0.0f;
+
+                lambda                = (lambda < 0) ? 0 : (lambda > 1) ? 1 : lambda;
+
+                pos = float3::Lerp(animChannel->positions[prevIndex], animChannel->positions[nextIndex], lambda);
+                /*GLOG(
+                    "Position interpolation: (%f,%f,%f), Lambda: %f, Times: %f to %f", pos.x, pos.y, pos.z, lambda,
+                    startTime, endTime
+                );*/
+            }
+        }
+    }
+
+    if (animChannel->numRotations > 0)
+    {
+        if (animChannel->numRotations == 1)
+        {
+            rot = animChannel->rotations[0].Normalized();
+            GLOG("Single rotation keyframe: (%f,%f,%f,%f)", rot.x, rot.y, rot.z, rot.w);
+        }
+        else
+        {
+            size_t nextIndex = 0;
+            while (nextIndex < animChannel->numRotations && animChannel->rotTimeStamps[nextIndex] <= currentTime)
+            {
+                nextIndex++;
+            }
+
+            size_t prevIndex = (nextIndex > 0) ? nextIndex - 1 : 0;
+
+            if (nextIndex >= animChannel->numRotations)
+            {
+                rot = animChannel->rotations[animChannel->numRotations - 1].Normalized();
+                // GLOG("Past last rotation keyframe: (%f,%f,%f,%f)", rot.x, rot.y, rot.z, rot.w);
+            }
+            else if (nextIndex == 0)
+            {
+                rot = animChannel->rotations[0].Normalized();
+                // GLOG("Before first rotation keyframe: (%f,%f,%f,%f)", rot.x, rot.y, rot.z, rot.w);
+            }
+            else
+            {
+                const float startTime = animChannel->rotTimeStamps[prevIndex];
+                const float endTime   = animChannel->rotTimeStamps[nextIndex];
+                const float timeDiff  = endTime - startTime;
+
+                float lambda          = (timeDiff > 0.0001f) ? (currentTime - startTime) / timeDiff : 0.0f;
+
+                lambda                = (lambda < 0) ? 0 : (lambda > 1) ? 1 : lambda;
+
+                rot = Interpolate(animChannel->rotations[prevIndex], animChannel->rotations[nextIndex], lambda);
+
+                /*GLOG(
+                    "Rotation interpolation: (%f,%f,%f,%f), Lambda: %f, Times: %f to %f", rot.x, rot.y, rot.z, rot.w,
+                    lambda, startTime, endTime
+                );*/
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/SobrassadaEngine/Scene/Components/Standalone/AnimController.h b/SobrassadaEngine/Scene/Components/Standalone/AnimController.h
new file mode 100644
index 000000000..61c8b1490
--- /dev/null
+++ b/SobrassadaEngine/Scene/Components/Standalone/AnimController.h
@@ -0,0 +1,39 @@
+#pragma once
+#include "Globals.h"
+
+class ResourceAnimation;
+
+class AnimController
+{
+  public:
+    AnimController();
+    ~AnimController();
+
+    update_status Update(float deltaTime);
+    void Play(UID resource, bool loop);
+    void Stop();
+    void Pause() { playAnimation = false; }
+    void Resume(){ playAnimation = true; }
+
+    void GetTransform(const std::string& nodeName, float3& pos, Quat& rot);
+    ResourceAnimation* GetCurrentAnimation() { return animation; }
+    float GetTime() const { return currentTime; }
+
+    void SetAnimationResource(ResourceAnimation* anim) { animation = anim; }
+    void SetPlaybackSpeed(float speed) { playbackSpeed = speed; }
+    void SetTime(float time) { currentTime = time; }
+
+    bool IsPlaying() const { return playAnimation; }
+
+  private:
+    Quat Interpolate(Quat& first, Quat& second, float lambda);
+
+  private: 
+
+    UID resource;
+    float currentTime            = 0;
+    bool loop                    = false;
+    bool playAnimation           = false;
+    float playbackSpeed          = 1.0f;
+    ResourceAnimation* animation = nullptr;
+};
\ No newline at end of file
diff --git a/SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.cpp b/SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.cpp
new file mode 100644
index 000000000..c7524e57e
--- /dev/null
+++ b/SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.cpp
@@ -0,0 +1,380 @@
+#include "AnimationComponent.h"
+
+#include "AnimController.h"
+#include "Application.h"
+#include "CameraModule.h"
+#include "EditorUIModule.h"
+#include "GameObject.h"
+#include "LibraryModule.h"
+#include "Resource.h"
+#include "ResourceAnimation.h"
+#include "ResourcesModule.h"
+#include "SceneModule.h"
+
+#include "imgui.h"
+#include "Math/Quat.h"
+
+AnimationComponent::AnimationComponent(const UID uid, GameObject* parent)
+    : Component(uid, parent, "Animation", COMPONENT_ANIMATION)
+{
+    animController = new AnimController();
+}
+
+AnimationComponent::AnimationComponent(const rapidjson::Value& initialState, GameObject* parent)
+    : Component(initialState, parent)
+{
+    animController = new AnimController();
+    if (initialState.HasMember("Animations") && initialState["Animations"].IsUint64())
+    {
+        resource = initialState["Animations"].GetUint64();
+    }
+    else
+    {
+        resource = 0;
+    }
+}
+
+AnimationComponent::~AnimationComponent()
+{
+    if (animController != nullptr)
+    {
+        delete animController;
+        animController = nullptr;
+    }
+    App->GetResourcesModule()->ReleaseResource(currentAnimResource);
+}
+
+void AnimationComponent::OnPlay()
+{
+    if (animController != nullptr && resource != 0)
+    {
+        animController->Play(resource, true);
+    }
+}
+
+void AnimationComponent::OnStop()
+{
+    if (animController != nullptr)
+    {
+        animController->Stop();
+    }
+}
+
+void AnimationComponent::OnPause()
+{
+    if (animController != nullptr)
+    {
+        animController->Pause();
+    }
+}
+
+void AnimationComponent::OnResume()
+{
+    if (animController != nullptr)
+    {
+        animController->Resume();
+    }
+}
+
+void AnimationComponent::OnInspector()
+{
+
+    if (resource != 0)
+    {
+        currentAnimResource = dynamic_cast<ResourceAnimation*>(App->GetResourcesModule()->RequestResource(resource));
+
+        if (currentAnimResource != nullptr)
+        {
+            ImGui::Text("Animation: %s", currentAnimResource->GetName().c_str());
+            ImGui::Text("Duration: %.2f seconds", currentAnimResource->GetDuration());
+
+            if (animController != nullptr && ImGui::TreeNode("Channels"))
+            {
+                for (const auto& channel : currentAnimResource->channels)
+                {
+                    ImGui::Text("Bone: %s", channel.first.c_str());
+
+                    if (ImGui::TreeNode(channel.first.c_str()))
+                    {
+                        const Channel& ch = channel.second;
+
+                        ImGui::Text("Positions: %d", ch.numPositions);
+                        if (ch.numPositions > 0)
+                        {
+                            ImGui::Text("First Position Time: %.2f", ch.posTimeStamps.front());
+                            ImGui::Text("Last Position Time: %.2f", ch.posTimeStamps.back());
+                        }
+
+                        ImGui::Text("Rotations: %d", ch.numRotations);
+                        if (ch.numRotations > 0)
+                        {
+                            ImGui::Text("First Rotation Time: %.2f", ch.rotTimeStamps.front());
+                            ImGui::Text("Last Rotation Time: %.2f", ch.rotTimeStamps.back());
+                        }
+
+                        ImGui::TreePop();
+                    }
+                }
+                ImGui::TreePop();
+            }
+        }
+        else
+        {
+            ImGui::Text("Animation resource not found");
+        }
+    }
+    else
+    {
+        ImGui::Text("No animation assigned");
+    }
+
+    if (ImGui::CollapsingHeader("Object Selection", ImGuiTreeNodeFlags_DefaultOpen))
+    {
+
+        GameObject* selectedObj = App->GetSceneModule()->GetScene()->GetSelectedGameObject();
+        if (selectedObj && selectedObj == parent) currentAnimComp = this;
+
+        if (selectedObj)
+        {
+            ImGui::Text("Selected Object: %s", selectedObj->GetName().c_str());
+
+            currentAnimComp = static_cast<AnimationComponent*>(selectedObj->GetAnimationComponent());
+
+            if (currentAnimComp)
+            {
+                ImGui::Text("Animation Component Found");
+
+                // Get the current animation
+                ResourceAnimation* anim = currentAnimComp->GetCurrentAnimation();
+                if (anim)
+                {
+                    ImGui::Text("Current Animation: %s", anim->GetName().c_str());
+                    animationDuration = anim->GetDuration();
+
+                    // Display animation controls
+                    ImGui::Separator();
+                    ImGui::Text("Animation Controls");
+
+                    if (ImGui::Button("Play"))
+                    {
+                        playing = true;
+                        currentAnimComp->OnPlay();
+                    }
+
+                    ImGui::SameLine();
+
+                    if (ImGui::Button("Pause"))
+                    {
+                        playing = false;
+                        currentAnimComp->OnPause();
+                    }
+
+                    ImGui::SameLine();
+
+                    if (ImGui::Button("Stop"))
+                    {
+                        playing     = false;
+                        currentTime = 0.0f;
+                        currentAnimComp->OnStop();
+                    }
+
+                    if (ImGui::Button("Resume"))
+                    {
+                        playing = true;
+                        currentAnimComp->OnResume();
+                    }
+
+                    if (ImGui::SliderFloat("Timeline", &currentTime, 0.0f, animationDuration, "%.2f sec"))
+                    {
+                        // When user manually changes the time, update the animation controller
+                        if (currentAnimComp->GetAnimationController())
+                        {
+                            currentAnimComp->GetAnimationController()->SetTime(currentTime);
+                        }
+                    }
+
+                    // Bone visualization
+                    if (ImGui::TreeNode("Bone Mapping"))
+                    {
+                        if (currentAnimComp)
+                        {
+
+                            const auto& boneMap = currentAnimComp->GetBoneMapping();
+
+                            if (boneMap.empty())
+                            {
+                                ImGui::Text("No bones mapped. Animation might not apply correctly.");
+                            }
+                            else
+                            {
+                                ImGui::Text("%d bones mapped:", boneMap.size());
+                                for (const auto& pair : boneMap)
+                                {
+                                    bool foundInAnimation = false;
+
+                                    if (currentAnimComp->GetCurrentAnimation())
+                                    {
+                                        foundInAnimation =
+                                            currentAnimComp->GetCurrentAnimation()->channels.find(pair.first) !=
+                                            currentAnimComp->GetCurrentAnimation()->channels.end();
+                                    }
+
+                                    ImGui::TextColored(
+                                        foundInAnimation ? ImVec4(0, 1, 0, 1) : ImVec4(1, 0, 0, 1), "%s -> %s",
+                                        pair.first.c_str(), pair.second ? pair.second->GetName().c_str() : "NULL"
+                                    );
+                                }
+                            }
+                        }
+                        ImGui::TreePop();
+                    }
+                }
+                else
+                {
+                    ImGui::Text("No animation assigned to component");
+                }
+            }
+            else
+            {
+                ImGui::Text("No animation component found on selected object");
+            }
+        }
+        else
+        {
+            ImGui::Text("No object selected");
+        }
+    }
+
+    if (ImGui::CollapsingHeader("Animation Library"))
+    {
+
+        ImGui::Text("Available Animations:");
+    }
+
+    if (playing && currentAnimComp && currentAnimComp->GetAnimationController())
+    {
+
+        currentTime = currentAnimComp->GetAnimationController()->GetTime();
+
+        if (currentTime >= animationDuration)
+        {
+            currentTime = 0.0f;
+        }
+    }
+}
+
+void AnimationComponent::Render(float deltaTime)
+{
+}
+
+void AnimationComponent::Clone(const Component* other)
+{
+    if (other->GetType() == ComponentType::COMPONENT_ANIMATION)
+    {
+        const AnimationComponent* otherAnimation = static_cast<const AnimationComponent*>(other);
+
+        AddAnimation(otherAnimation->currentAnimResource->GetUID());
+    }
+    else
+    {
+        GLOG("It is not possible to clone a component of a different type!");
+    }
+}
+
+void AnimationComponent::Update(float deltaTime)
+{
+    if (!animController->IsPlaying()) return;
+
+    if (boneMapping.empty())
+    {
+        SetBoneMapping();
+    }
+
+    animController->Update(deltaTime);
+
+    for (auto& channel : currentAnimResource->channels)
+    {
+        const std::string& boneName = channel.first;
+
+        auto boneIt                 = boneMapping.find(boneName);
+
+        if (boneIt != boneMapping.end())
+        {
+            GameObject* bone = boneIt->second;
+            float3 position  = bone->GetLocalTransform().TranslatePart();
+            Quat rotation    = Quat(bone->GetLocalTransform().RotatePart());
+
+            animController->GetTransform(boneName, position, rotation);
+
+            float4x4 transformMatrix = float4x4::FromTRS(position, rotation, float3(1.0, 1.0, 1.0));
+            bone->SetLocalTransform(transformMatrix);
+            bone->OnTransformUpdated();
+        }
+    }
+}
+
+void AnimationComponent::Save(rapidjson::Value& targetState, rapidjson::Document::AllocatorType& allocator) const
+{
+    Component::Save(targetState, allocator);
+
+    targetState.AddMember(
+        "Animations", currentAnimResource != nullptr ? currentAnimResource->GetUID() : INVALID_UID, allocator
+    );
+}
+
+void AnimationComponent::AddAnimation(UID animationUID)
+{
+    if (animationUID == INVALID_UID) return;
+    if (currentAnimResource != nullptr && currentAnimResource->GetUID() == animationUID) return;
+
+    ResourceAnimation* newAnimation =
+        dynamic_cast<ResourceAnimation*>(App->GetResourcesModule()->RequestResource(animationUID));
+
+    if (newAnimation != nullptr)
+    {
+        App->GetResourcesModule()->ReleaseResource(currentAnimResource);
+        currentAnimResource = newAnimation;
+        currentAnimName     = currentAnimResource->GetName();
+        resource            = animationUID;
+        SetBoneMapping();
+    }
+}
+
+void AnimationComponent::SetAnimationResource(UID animResource)
+{
+    resource = animResource;
+    AddAnimation(resource);
+    GLOG("Setting animation resource: %llu", resource);
+}
+
+void AnimationComponent::SetBoneMapping()
+{
+    boneMapping.clear();
+
+    std::function<void(GameObject*)> mapBones = [this, &mapBones](GameObject* obj)
+    {
+        if (obj == nullptr) return;
+
+        boneMapping[obj->GetName()] = obj;
+
+        for (const UID childUID : obj->GetChildren())
+        {
+            GameObject* child = App->GetSceneModule()->GetScene()->GetGameObjectByUID(childUID);
+
+            if (child != nullptr)
+            {
+                mapBones(child);
+            }
+        }
+    };
+    mapBones(parent);
+}
+
+void AnimationComponent::RenderEditorInspector()
+{
+    Component::RenderEditorInspector();
+    if (enabled)
+    {
+        OnInspector();
+    }
+}
diff --git a/SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h b/SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h
new file mode 100644
index 000000000..ad56228de
--- /dev/null
+++ b/SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h
@@ -0,0 +1,58 @@
+#pragma once
+
+#include "Component.h"
+#include "Globals.h"
+
+#include "rapidjson/document.h"
+#include <cstdint>
+#include <unordered_map>
+
+class ResourceAnimation;
+class AnimController;
+class GameObject;
+
+class AnimationComponent : public Component
+{
+  public:
+    AnimationComponent(UID uid, GameObject* parent);
+    AnimationComponent(const rapidjson::Value& initialState, GameObject* parent);
+    ~AnimationComponent() override;
+
+    void Clone(const Component* other) override;
+    void Update(float deltaTime) override;
+    void Render(float deltaTime) override;
+    void RenderEditorInspector() override;
+    void Save(rapidjson::Value& targetState, rapidjson::Document::AllocatorType& allocator) const override;
+
+    void OnPlay();
+    void OnStop();
+    void OnPause();
+    void OnResume();
+    void OnInspector();
+    void AddAnimation(UID resource);
+
+    UID GetAnimationResource() const { GLOG("Resource AnimUID  is: %d", resource) return resource; }
+    ResourceAnimation* GetCurrentAnimation() const { return currentAnimResource; }
+    AnimController* GetAnimationController() { return animController; }
+    std::unordered_map<std::string, GameObject*> GetBoneMapping() const { return boneMapping; }
+
+    void SetAnimationResource(UID animResource);
+
+  private:
+    void SetBoneMapping();
+
+  private:
+    UID resource                           = INVALID_UID;
+    std::string currentAnimName            = "None";
+    AnimationComponent* currentAnimComp    = nullptr;
+
+    AnimController* animController         = nullptr;
+    ResourceAnimation* currentAnimResource = nullptr;
+
+    GameObject* owner                      = nullptr;
+    std::unordered_map<std::string, GameObject*> boneMapping;
+
+    float animationDuration = 0.0f;
+    bool playing            = false;
+    float currentTime       = 0.0f;
+};
diff --git a/SobrassadaEngine/Scene/GameObjects/GameObject.cpp b/SobrassadaEngine/Scene/GameObjects/GameObject.cpp
index e4fe317b7..881295e43 100644
--- a/SobrassadaEngine/Scene/GameObjects/GameObject.cpp
+++ b/SobrassadaEngine/Scene/GameObjects/GameObject.cpp
@@ -7,6 +7,7 @@
 #include "PrefabManager.h"
 #include "SceneModule.h"
 #include "Standalone/MeshComponent.h"
+#include "Standalone/AnimationComponent.h"
 #include "Standalone/UI/Transform2DComponent.h"
 
 #include "imgui.h"
@@ -398,6 +399,15 @@ MeshComponent* GameObject::GetMeshComponent() const
     return nullptr;
 }
 
+AnimationComponent* GameObject::GetAnimationComponent() const
+{
+    if (components.find(COMPONENT_ANIMATION) != components.end())
+    {
+        return dynamic_cast<AnimationComponent*>(components.at(COMPONENT_ANIMATION));
+    }
+    return nullptr;
+}
+
 void GameObject::OnTransformUpdated()
 {
     globalTransform              = GetParentGlobalTransform() * localTransform;
diff --git a/SobrassadaEngine/Scene/GameObjects/GameObject.h b/SobrassadaEngine/Scene/GameObjects/GameObject.h
index 9843ac78e..60b63dcae 100644
--- a/SobrassadaEngine/Scene/GameObjects/GameObject.h
+++ b/SobrassadaEngine/Scene/GameObjects/GameObject.h
@@ -11,6 +11,7 @@
 #include <vector>
 
 class MeshComponent;
+class AnimationComponent;
 
 enum MobilitySettings
 {
@@ -87,25 +88,28 @@ class SOBRASADA_API_ENGINE GameObject
     Component* GetComponentByType(ComponentType type) const;
 
     MeshComponent* GetMeshComponent() const;
+
+   AnimationComponent* GetAnimationComponent() const;
+
     const float3& GetPosition() const { return position; }
     const float3& GetRotation() const { return rotation; }
     const float3& GetScale() const { return scale; }
+    AABB GetHierarchyAABB();
 
+    
     void SetLocalTransform(const float4x4& newTransform);
     void DrawGizmos() const;
 
     void CreatePrefab();
     UID GetPrefabUID() const { return prefabUID; }
     void SetPrefabUID(const UID uid) { prefabUID = uid; }
-
-    void OnTransformUpdated();
     void UpdateComponents();
-    AABB GetHierarchyAABB();
-
+    void OnTransformUpdated();
     void SetPosition(float3& newPosition) { position = newPosition; };
     void SetWillUpdate(bool willUpdate) { this->willUpdate = willUpdate; };
     bool WillUpdate() const { return willUpdate; };
 
+
   private:
     void UpdateLocalTransform(const float4x4& parentGlobalTransform);
     void DrawNodes() const;
diff --git a/SobrassadaEngine/Scene/Scene.cpp b/SobrassadaEngine/Scene/Scene.cpp
index 88a0efa7b..4b56266c1 100644
--- a/SobrassadaEngine/Scene/Scene.cpp
+++ b/SobrassadaEngine/Scene/Scene.cpp
@@ -27,6 +27,7 @@
 #include "ResourcesModule.h"
 #include "SceneModule.h"
 #include "Standalone/MeshComponent.h"
+#include "Standalone/AnimationComponent.h"
 
 #include "SDL_mouse.h"
 #include "imgui.h"
@@ -748,7 +749,16 @@ void Scene::LoadModel(const UID modelUID)
         gameObjectsArray.resize(allNodes.size());
         std::vector<UID> gameObjectsUID;
         std::vector<GameObject*> rootGameObjects;
+        
+        GLOG("Model Animation UID: %llu", newModel->GetAnimationUID());
 
+        const auto& animUIDs = newModel->GetAllAnimationUIDs();
+        GLOG("Total Animation UIDs %zu ", animUIDs.size());
+
+        for (UID uid : animUIDs)
+        {
+            GLOG("Animation UID in list: %llu ", uid);
+        }
         for (unsigned int i = 0; i < allNodes.size(); ++i)
         {
             gameObjectsUID.push_back(GenerateUID());
@@ -890,6 +900,25 @@ void Scene::LoadModel(const UID modelUID)
         }
         for (GameObject* rootGameObject : rootGameObjects)
         {
+            if (!animUIDs.empty())
+            {
+                rootGameObject->CreateComponent(COMPONENT_ANIMATION);
+                AnimationComponent* animComponent = rootGameObject->GetAnimationComponent();
+
+                GLOG("Model has %zu animations", animUIDs.size());
+                for (UID uid : animUIDs)
+                {
+                    GLOG("Setting aimation resource with UID %llu ", uid);
+                    animComponent->SetAnimationResource(uid);
+
+                    GLOG("Animation UID: %d", uid);
+                }
+
+            }
+            else
+            {
+                GLOG("No animations found for this model");
+            }
             rootGameObject->UpdateTransformForGOBranch();
         }
     }
diff --git a/SobrassadaEngine/SobrassadaEngine.vcxproj b/SobrassadaEngine/SobrassadaEngine.vcxproj
index 3f2c748aa..84dcf16c5 100644
--- a/SobrassadaEngine/SobrassadaEngine.vcxproj
+++ b/SobrassadaEngine/SobrassadaEngine.vcxproj
@@ -286,6 +286,8 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="Application.cpp" />
+    <ClCompile Include="FileSystem\Importers\AnimationImporter.cpp" />
+    <ClCompile Include="FileSystem\Meta\MetaAnimation.cpp" />
     <ClCompile Include="FileSystem\Importers\FontImporter.cpp" />
     <ClCompile Include="FileSystem\Meta\MetaFont.cpp" />
     <ClCompile Include="Scene\Components\Standalone\Physics\CapsuleColliderComponent.cpp" />
@@ -302,6 +304,9 @@
     <ClCompile Include="FileSystem\Batching\GeometryBatch.cpp" />
     <ClCompile Include="Libs\ImNodeFlow-1.2.2\src\ImNodeFlow.cpp" />
     <ClCompile Include="FileSystem\StateMachineManager.cpp" />
+    <ClCompile Include="ResourceManagement\Resources\ResourceAnimation.cpp" />
+    <ClCompile Include="Scene\Components\Standalone\AnimationComponent.cpp" />
+    <ClCompile Include="Scene\Components\Standalone\AnimController.cpp" />
     <ClCompile Include="Modules\PhysicsModule.cpp" />
     <ClCompile Include="ResourceManagement\Resources\ResourceNavmesh.cpp" />
     <ClCompile Include="Modules\GameUIModule.cpp" />
@@ -482,6 +487,9 @@
     <ClInclude Include="Libs\ImNodeFlow-1.2.2\src\imgui_bezier_math.h" />
     <ClInclude Include="Libs\ImNodeFlow-1.2.2\src\imgui_extra_math.h" />
     <ClInclude Include="FileSystem\StateMachineManager.h" />
+    <ClInclude Include="ResourceManagement\Resources\ResourceAnimation.h" />
+    <ClInclude Include="Scene\Components\Standalone\AnimationComponent.h" />
+    <ClInclude Include="Scene\Components\Standalone\AnimController.h" />
     <ClInclude Include="Modules\GameUIModule.h" />
     <ClInclude Include="ResourceManagement\Resources\ResourceFont.h" />
     <ClInclude Include="Scene\Components\Standalone\UI\CanvasComponent.h" />
@@ -510,12 +518,14 @@
     <ClInclude Include="FileSystem\Meta\MetaMaterial.h" />
     <ClInclude Include="FileSystem\Meta\MetaMesh.h" />
     <ClInclude Include="FileSystem\Meta\MetaTexture.h" />
+     <ClInclude Include="FileSystem\Meta\MetaAnimation.h" />
     <ClInclude Include="Modules\CameraModule.h" />
     <ClInclude Include="Modules\EditorUIModule.h" />
     <ClInclude Include="Modules\ProjectModule.h" />
     <ClInclude Include="Modules\ResourcesModule.h" />
     <ClInclude Include="Modules\SceneModule.h" />
     <ClInclude Include="FileSystem\Importers\ModelImporter.h" />
+    <ClInclude Include="FileSystem\Importers\AnimationImporter.h" />
     <ClInclude Include="UI\EngineEditors\Nodes\StateNode.h" />
     <ClInclude Include="Scene\Components\Standalone\UI\Transform2DComponent.h" />
     <ClInclude Include="Utils\Delegate.h" />
diff --git a/SobrassadaEngine/SobrassadaEngine.vcxproj.filters b/SobrassadaEngine/SobrassadaEngine.vcxproj.filters
index bb742f99e..8641923ac 100644
--- a/SobrassadaEngine/SobrassadaEngine.vcxproj.filters
+++ b/SobrassadaEngine/SobrassadaEngine.vcxproj.filters
@@ -384,6 +384,21 @@
     <ClCompile Include="FileSystem\StateMachineManager.cpp">
       <Filter>FileSystem</Filter>
     </ClCompile>
+    <ClCompile Include="FileSystem\Importers\AnimationImporter.cpp">
+      <Filter>FileSystem\Importers</Filter>
+    </ClCompile>
+    <ClCompile Include="FileSystem\Meta\MetaAnimation.cpp">
+      <Filter>FileSystem\Meta</Filter>
+    </ClCompile>
+    <ClCompile Include="Scene\Components\Standalone\AnimationComponent.cpp">
+      <Filter>Scene\Components</Filter>
+    </ClCompile>
+    <ClCompile Include="Scene\Components\Standalone\AnimController.cpp">
+      <Filter>Scene\Components</Filter>
+    </ClCompile>
+    <ClCompile Include="ResourceManagement\Resources\ResourceAnimation.cpp">
+      <Filter>Resources</Filter>
+      </ClCompile>
     <ClCompile Include="Scene\Components\Standalone\UI\CanvasComponent.cpp">
       <Filter>Scene\Components</Filter>
     </ClCompile>
@@ -785,6 +800,21 @@
     <ClInclude Include="FileSystem\Config\ProjectConfig.h">
       <Filter>FileSystem\Config</Filter>
     </ClInclude>
+    <ClInclude Include="FileSystem\Importers\AnimationImporter.h">
+      <Filter>FileSystem\Importers</Filter>
+    </ClInclude>
+    <ClInclude Include="FileSystem\Meta\MetaAnimation.h">
+      <Filter>FileSystem\Meta</Filter>
+    </ClInclude>
+    <ClInclude Include="Scene\Components\Standalone\AnimationComponent.h">
+      <Filter>Scene\Components</Filter>
+    </ClInclude>
+    <ClInclude Include="Scene\Components\Standalone\AnimController.h">
+      <Filter>Scene\Components</Filter>
+    </ClInclude>
+    <ClInclude Include="ResourceManagement\Resources\ResourceAnimation.h">
+      <Filter>Resources</Filter>
+    </ClInclude>
     <ClInclude Include="Utils\TextManager.h">
       <Filter>Utils</Filter>
     </ClInclude>
diff --git a/SobrassadaEngine/Utils/Globals.h b/SobrassadaEngine/Utils/Globals.h
index a387d915a..82660b850 100644
--- a/SobrassadaEngine/Utils/Globals.h
+++ b/SobrassadaEngine/Utils/Globals.h
@@ -94,6 +94,7 @@ constexpr const char* SCENES_PATH                    = "Assets/Scenes/";
 constexpr const char* METADATA_PATH                  = "Assets/Metadata/";
 constexpr const char* PREFABS_ASSETS_PATH            = "Assets/Prefabs/";
 constexpr const char* MODELS_ASSETS_PATH             = "Assets/Models/";
+constexpr const char* ANIMATIONS_ASSETS_PATH         = "Assets/Animations/";
 constexpr const char* STATEMACHINES_ASSETS_PATH      = "Assets/StateMachines/";
 
 constexpr const char* LIBRARY_PATH                   = "Library/";
@@ -115,6 +116,7 @@ constexpr const char* MATERIAL_EXTENSION             = ".mat";
 constexpr const char* SCENE_EXTENSION                = ".scene";
 constexpr const char* PREFAB_EXTENSION               = ".prefab";
 constexpr const char* MODEL_EXTENSION                = ".model";
+constexpr const char* ANIMATION_EXTENSION            = ".anim";
 constexpr const char* META_EXTENSION                 = ".smeta";
 constexpr const char* STATEMACHINE_EXTENSION         = ".smachine";
 constexpr const char* FONT_EXTENSION                 = ".ttf";


Reviews:
user: login: SirKaza
body: Couldn't test it, but I will once there are some things fixed/changed.
Here are some things that I found:
· Have a different project from the engine path, so then all the assets will not be uploaded to GitHub.
· resourceModel→SetAnimationUID(loadedAnimations[0]); what if loadedAnimations is 0? (ModelImporter.cpp line 447) its crashing me here.
· Format everything before merge.
· Delete the files AnimationEditor from the engine if they are not used.
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T10:55:07Z
user: login: SirKaza
body: Tested in debug and release with zoombunny, works as expected.
Good job with the implementation and good use of const.
Merge with main & ready to go!
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-08T23:39:42Z
user: login: IDarkgenesis
body: Works as expected, very good job, please try merge with last changes from Marc that changes how models are loaded and now they load correctly, notify on Discord as soon as the change is done and I will change the status ASAP.

👍 
state: CHANGES_REQUESTED
author_association: CONTRIBUTOR
submitted_at: 2025-04-10T23:49:38Z
user: login: IDarkgenesis
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-04-11T16:35:44Z

Review comments:
diff_hunk: @@ -0,0 +1,221 @@
+#include "AnimationImporter.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "LibraryModule.h"
+#include "MetaAnimation.h"
+#include "ProjectModule.h"
+#include "ResourceAnimation.h"
+
+#include <Libs/rapidjson/document.h>
path: SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp
user: login: SirKaza
body: #include "rapidjson/document.h"
created_at: 2025-04-08T09:45:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,221 @@
+#include "AnimationImporter.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "LibraryModule.h"
+#include "MetaAnimation.h"
+#include "ProjectModule.h"
+#include "ResourceAnimation.h"
+
+#include <Libs/rapidjson/document.h>
+#include <memory>
+#include <tiny_gltf.h>
+#include <vector>
+
+namespace AnimationImporter
+{
+    UID ImportAnimation(
+        const tinygltf::Model& model, const tinygltf::Animation& animation, const std::string& name, const char* sourceFilePath,
+        const std::string& targetFilePath, UID sourceUID
+    )
+    {
+        std::vector<char> buffer;
+
+        uint32_t channelCount = static_cast<uint32_t>(animation.channels.size());
+        buffer.insert(
+            buffer.end(), reinterpret_cast<char*>(&channelCount),
+            reinterpret_cast<char*>(&channelCount) + sizeof(uint32_t)
+        );
+
+        for (const auto& channel : animation.channels)
+        {
+            const tinygltf::AnimationSampler& sampler = animation.samplers[channel.sampler];
+
+            const tinygltf::Accessor& input           = model.accessors[sampler.input];
+            const tinygltf::Accessor& output          = model.accessors[sampler.output];
+
+            const tinygltf::BufferView& inputView     = model.bufferViews[input.bufferView];
+            const tinygltf::BufferView& outputView    = model.bufferViews[output.bufferView];
+
+            const tinygltf::Buffer& inputBuffer       = model.buffers[inputView.buffer];
+            const tinygltf::Buffer& outputBuffer      = model.buffers[outputView.buffer];
+
+            const float* timeStamps =
+                reinterpret_cast<const float*>(&inputBuffer.data[input.byteOffset + inputView.byteOffset]);
+            const float* values =
+                reinterpret_cast<const float*>(&outputBuffer.data[output.byteOffset + outputView.byteOffset]);
+
+            size_t keyframeCount        = input.count;
+
+            const std::string& nodeName = model.nodes[channel.target_node].name;
+            const uint32_t nameSize     = static_cast<uint32_t>(nodeName.size());
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&nameSize),
+                reinterpret_cast<const char*>(&nameSize) + sizeof(uint32_t)
+            );
+            buffer.insert(buffer.end(), nodeName.begin(), nodeName.end());
+
+            AnimationType animType = AnimationType::TRANSLATION;
+
+            if (channel.target_path == "rotation")
+            {
+                animType = AnimationType::ROTATION;
+            }
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&animType),
+                reinterpret_cast<const char*>(&animType) + sizeof(AnimationType)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&keyframeCount),
+                reinterpret_cast<const char*>(&keyframeCount) + sizeof(uint32_t)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(timeStamps),
+                reinterpret_cast<const char*>(timeStamps) + keyframeCount * sizeof(float)
+            );
+
+            if (animType == AnimationType::TRANSLATION)
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(float3)
+                );
+            }
+            else
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(Quat)
+                );
+            }
+        }
+
+        // Handle UID
+        const std::string fileName = FileSystem::GetFileNameWithoutExtension(sourceFilePath);
+        UID finalAnimUID;
+        if (sourceUID == INVALID_UID)
+        {
+            UID animationUID      = GenerateUID();
+            finalAnimUID          = App->GetLibraryModule()->AssignFiletypeUID(animationUID, FileType::Animation);
+
+            std::string assetPath = ANIMATIONS_PATH + FileSystem::GetFileNameWithExtension(sourceFilePath);
+            MetaAnimation meta(finalAnimUID, assetPath);
+
+            
+            
+            meta.Save(fileName, assetPath);
+        }
+        else
+        {
+            finalAnimUID = sourceUID;
+        }
+
+        // Construct save paths 
+
+        std::string saveFilePath = App->GetProjectModule()->GetLoadedProjectPath() + ANIMATIONS_PATH +
+                                   std::to_string(finalAnimUID) + ANIMATION_EXTENSION;
+      
+        unsigned int bytesWritten =
+            (unsigned int)FileSystem::Save(saveFilePath.c_str(), buffer.data(), buffer.size(), true);
path: SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp
user: login: SirKaza
body: warning C4267: 'argument': conversion from 'size_t' to 'unsigned int', possible loss of data
created_at: 2025-04-08T09:50:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,221 @@
+#include "AnimationImporter.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "LibraryModule.h"
+#include "MetaAnimation.h"
+#include "ProjectModule.h"
+#include "ResourceAnimation.h"
+
+#include <Libs/rapidjson/document.h>
+#include <memory>
+#include <tiny_gltf.h>
+#include <vector>
+
+namespace AnimationImporter
+{
+    UID ImportAnimation(
+        const tinygltf::Model& model, const tinygltf::Animation& animation, const std::string& name, const char* sourceFilePath,
+        const std::string& targetFilePath, UID sourceUID
+    )
+    {
+        std::vector<char> buffer;
+
+        uint32_t channelCount = static_cast<uint32_t>(animation.channels.size());
+        buffer.insert(
+            buffer.end(), reinterpret_cast<char*>(&channelCount),
+            reinterpret_cast<char*>(&channelCount) + sizeof(uint32_t)
+        );
+
+        for (const auto& channel : animation.channels)
+        {
+            const tinygltf::AnimationSampler& sampler = animation.samplers[channel.sampler];
+
+            const tinygltf::Accessor& input           = model.accessors[sampler.input];
+            const tinygltf::Accessor& output          = model.accessors[sampler.output];
+
+            const tinygltf::BufferView& inputView     = model.bufferViews[input.bufferView];
+            const tinygltf::BufferView& outputView    = model.bufferViews[output.bufferView];
+
+            const tinygltf::Buffer& inputBuffer       = model.buffers[inputView.buffer];
+            const tinygltf::Buffer& outputBuffer      = model.buffers[outputView.buffer];
+
+            const float* timeStamps =
+                reinterpret_cast<const float*>(&inputBuffer.data[input.byteOffset + inputView.byteOffset]);
+            const float* values =
+                reinterpret_cast<const float*>(&outputBuffer.data[output.byteOffset + outputView.byteOffset]);
+
+            size_t keyframeCount        = input.count;
path: SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp
user: login: SirKaza
body: const
created_at: 2025-04-08T09:54:14Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,221 @@
+#include "AnimationImporter.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "LibraryModule.h"
+#include "MetaAnimation.h"
+#include "ProjectModule.h"
+#include "ResourceAnimation.h"
+
+#include <Libs/rapidjson/document.h>
+#include <memory>
+#include <tiny_gltf.h>
+#include <vector>
+
+namespace AnimationImporter
+{
+    UID ImportAnimation(
+        const tinygltf::Model& model, const tinygltf::Animation& animation, const std::string& name, const char* sourceFilePath,
+        const std::string& targetFilePath, UID sourceUID
+    )
+    {
+        std::vector<char> buffer;
+
+        uint32_t channelCount = static_cast<uint32_t>(animation.channels.size());
+        buffer.insert(
+            buffer.end(), reinterpret_cast<char*>(&channelCount),
+            reinterpret_cast<char*>(&channelCount) + sizeof(uint32_t)
+        );
+
+        for (const auto& channel : animation.channels)
+        {
+            const tinygltf::AnimationSampler& sampler = animation.samplers[channel.sampler];
+
+            const tinygltf::Accessor& input           = model.accessors[sampler.input];
+            const tinygltf::Accessor& output          = model.accessors[sampler.output];
+
+            const tinygltf::BufferView& inputView     = model.bufferViews[input.bufferView];
+            const tinygltf::BufferView& outputView    = model.bufferViews[output.bufferView];
+
+            const tinygltf::Buffer& inputBuffer       = model.buffers[inputView.buffer];
+            const tinygltf::Buffer& outputBuffer      = model.buffers[outputView.buffer];
+
+            const float* timeStamps =
+                reinterpret_cast<const float*>(&inputBuffer.data[input.byteOffset + inputView.byteOffset]);
+            const float* values =
+                reinterpret_cast<const float*>(&outputBuffer.data[output.byteOffset + outputView.byteOffset]);
+
+            size_t keyframeCount        = input.count;
+
+            const std::string& nodeName = model.nodes[channel.target_node].name;
+            const uint32_t nameSize     = static_cast<uint32_t>(nodeName.size());
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&nameSize),
+                reinterpret_cast<const char*>(&nameSize) + sizeof(uint32_t)
+            );
+            buffer.insert(buffer.end(), nodeName.begin(), nodeName.end());
+
+            AnimationType animType = AnimationType::TRANSLATION;
+
+            if (channel.target_path == "rotation")
+            {
+                animType = AnimationType::ROTATION;
+            }
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&animType),
+                reinterpret_cast<const char*>(&animType) + sizeof(AnimationType)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&keyframeCount),
+                reinterpret_cast<const char*>(&keyframeCount) + sizeof(uint32_t)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(timeStamps),
+                reinterpret_cast<const char*>(timeStamps) + keyframeCount * sizeof(float)
+            );
+
+            if (animType == AnimationType::TRANSLATION)
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(float3)
+                );
+            }
+            else
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(Quat)
+                );
+            }
+        }
+
+        // Handle UID
+        const std::string fileName = FileSystem::GetFileNameWithoutExtension(sourceFilePath);
+        UID finalAnimUID;
+        if (sourceUID == INVALID_UID)
+        {
+            UID animationUID      = GenerateUID();
path: SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp
user: login: SirKaza
body: const
created_at: 2025-04-08T09:54:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,221 @@
+#include "AnimationImporter.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "LibraryModule.h"
+#include "MetaAnimation.h"
+#include "ProjectModule.h"
+#include "ResourceAnimation.h"
+
+#include <Libs/rapidjson/document.h>
+#include <memory>
+#include <tiny_gltf.h>
+#include <vector>
+
+namespace AnimationImporter
+{
+    UID ImportAnimation(
+        const tinygltf::Model& model, const tinygltf::Animation& animation, const std::string& name, const char* sourceFilePath,
+        const std::string& targetFilePath, UID sourceUID
+    )
+    {
+        std::vector<char> buffer;
+
+        uint32_t channelCount = static_cast<uint32_t>(animation.channels.size());
+        buffer.insert(
+            buffer.end(), reinterpret_cast<char*>(&channelCount),
+            reinterpret_cast<char*>(&channelCount) + sizeof(uint32_t)
+        );
+
+        for (const auto& channel : animation.channels)
+        {
+            const tinygltf::AnimationSampler& sampler = animation.samplers[channel.sampler];
+
+            const tinygltf::Accessor& input           = model.accessors[sampler.input];
+            const tinygltf::Accessor& output          = model.accessors[sampler.output];
+
+            const tinygltf::BufferView& inputView     = model.bufferViews[input.bufferView];
+            const tinygltf::BufferView& outputView    = model.bufferViews[output.bufferView];
+
+            const tinygltf::Buffer& inputBuffer       = model.buffers[inputView.buffer];
+            const tinygltf::Buffer& outputBuffer      = model.buffers[outputView.buffer];
+
+            const float* timeStamps =
+                reinterpret_cast<const float*>(&inputBuffer.data[input.byteOffset + inputView.byteOffset]);
+            const float* values =
+                reinterpret_cast<const float*>(&outputBuffer.data[output.byteOffset + outputView.byteOffset]);
+
+            size_t keyframeCount        = input.count;
+
+            const std::string& nodeName = model.nodes[channel.target_node].name;
+            const uint32_t nameSize     = static_cast<uint32_t>(nodeName.size());
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&nameSize),
+                reinterpret_cast<const char*>(&nameSize) + sizeof(uint32_t)
+            );
+            buffer.insert(buffer.end(), nodeName.begin(), nodeName.end());
+
+            AnimationType animType = AnimationType::TRANSLATION;
+
+            if (channel.target_path == "rotation")
+            {
+                animType = AnimationType::ROTATION;
+            }
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&animType),
+                reinterpret_cast<const char*>(&animType) + sizeof(AnimationType)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&keyframeCount),
+                reinterpret_cast<const char*>(&keyframeCount) + sizeof(uint32_t)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(timeStamps),
+                reinterpret_cast<const char*>(timeStamps) + keyframeCount * sizeof(float)
+            );
+
+            if (animType == AnimationType::TRANSLATION)
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(float3)
+                );
+            }
+            else
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(Quat)
+                );
+            }
+        }
+
+        // Handle UID
+        const std::string fileName = FileSystem::GetFileNameWithoutExtension(sourceFilePath);
+        UID finalAnimUID;
+        if (sourceUID == INVALID_UID)
+        {
+            UID animationUID      = GenerateUID();
+            finalAnimUID          = App->GetLibraryModule()->AssignFiletypeUID(animationUID, FileType::Animation);
+
+            std::string assetPath = ANIMATIONS_PATH + FileSystem::GetFileNameWithExtension(sourceFilePath);
path: SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp
user: login: SirKaza
body: const
created_at: 2025-04-08T09:55:04Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,221 @@
+#include "AnimationImporter.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "LibraryModule.h"
+#include "MetaAnimation.h"
+#include "ProjectModule.h"
+#include "ResourceAnimation.h"
+
+#include <Libs/rapidjson/document.h>
+#include <memory>
+#include <tiny_gltf.h>
+#include <vector>
+
+namespace AnimationImporter
+{
+    UID ImportAnimation(
+        const tinygltf::Model& model, const tinygltf::Animation& animation, const std::string& name, const char* sourceFilePath,
+        const std::string& targetFilePath, UID sourceUID
+    )
+    {
+        std::vector<char> buffer;
+
+        uint32_t channelCount = static_cast<uint32_t>(animation.channels.size());
+        buffer.insert(
+            buffer.end(), reinterpret_cast<char*>(&channelCount),
+            reinterpret_cast<char*>(&channelCount) + sizeof(uint32_t)
+        );
+
+        for (const auto& channel : animation.channels)
+        {
+            const tinygltf::AnimationSampler& sampler = animation.samplers[channel.sampler];
+
+            const tinygltf::Accessor& input           = model.accessors[sampler.input];
+            const tinygltf::Accessor& output          = model.accessors[sampler.output];
+
+            const tinygltf::BufferView& inputView     = model.bufferViews[input.bufferView];
+            const tinygltf::BufferView& outputView    = model.bufferViews[output.bufferView];
+
+            const tinygltf::Buffer& inputBuffer       = model.buffers[inputView.buffer];
+            const tinygltf::Buffer& outputBuffer      = model.buffers[outputView.buffer];
+
+            const float* timeStamps =
+                reinterpret_cast<const float*>(&inputBuffer.data[input.byteOffset + inputView.byteOffset]);
+            const float* values =
+                reinterpret_cast<const float*>(&outputBuffer.data[output.byteOffset + outputView.byteOffset]);
+
+            size_t keyframeCount        = input.count;
+
+            const std::string& nodeName = model.nodes[channel.target_node].name;
+            const uint32_t nameSize     = static_cast<uint32_t>(nodeName.size());
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&nameSize),
+                reinterpret_cast<const char*>(&nameSize) + sizeof(uint32_t)
+            );
+            buffer.insert(buffer.end(), nodeName.begin(), nodeName.end());
+
+            AnimationType animType = AnimationType::TRANSLATION;
+
+            if (channel.target_path == "rotation")
+            {
+                animType = AnimationType::ROTATION;
+            }
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&animType),
+                reinterpret_cast<const char*>(&animType) + sizeof(AnimationType)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&keyframeCount),
+                reinterpret_cast<const char*>(&keyframeCount) + sizeof(uint32_t)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(timeStamps),
+                reinterpret_cast<const char*>(timeStamps) + keyframeCount * sizeof(float)
+            );
+
+            if (animType == AnimationType::TRANSLATION)
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(float3)
+                );
+            }
+            else
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(Quat)
+                );
+            }
+        }
+
+        // Handle UID
+        const std::string fileName = FileSystem::GetFileNameWithoutExtension(sourceFilePath);
+        UID finalAnimUID;
+        if (sourceUID == INVALID_UID)
+        {
+            UID animationUID      = GenerateUID();
+            finalAnimUID          = App->GetLibraryModule()->AssignFiletypeUID(animationUID, FileType::Animation);
+
+            std::string assetPath = ANIMATIONS_PATH + FileSystem::GetFileNameWithExtension(sourceFilePath);
+            MetaAnimation meta(finalAnimUID, assetPath);
+
+            
+            
+            meta.Save(fileName, assetPath);
+        }
+        else
+        {
+            finalAnimUID = sourceUID;
+        }
+
+        // Construct save paths 
+
+        std::string saveFilePath = App->GetProjectModule()->GetLoadedProjectPath() + ANIMATIONS_PATH +
path: SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp
user: login: SirKaza
body: const
created_at: 2025-04-08T09:55:59Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,221 @@
+#include "AnimationImporter.h"
+
+#include "Application.h"
+#include "FileSystem.h"
+#include "LibraryModule.h"
+#include "MetaAnimation.h"
+#include "ProjectModule.h"
+#include "ResourceAnimation.h"
+
+#include <Libs/rapidjson/document.h>
+#include <memory>
+#include <tiny_gltf.h>
+#include <vector>
+
+namespace AnimationImporter
+{
+    UID ImportAnimation(
+        const tinygltf::Model& model, const tinygltf::Animation& animation, const std::string& name, const char* sourceFilePath,
+        const std::string& targetFilePath, UID sourceUID
+    )
+    {
+        std::vector<char> buffer;
+
+        uint32_t channelCount = static_cast<uint32_t>(animation.channels.size());
+        buffer.insert(
+            buffer.end(), reinterpret_cast<char*>(&channelCount),
+            reinterpret_cast<char*>(&channelCount) + sizeof(uint32_t)
+        );
+
+        for (const auto& channel : animation.channels)
+        {
+            const tinygltf::AnimationSampler& sampler = animation.samplers[channel.sampler];
+
+            const tinygltf::Accessor& input           = model.accessors[sampler.input];
+            const tinygltf::Accessor& output          = model.accessors[sampler.output];
+
+            const tinygltf::BufferView& inputView     = model.bufferViews[input.bufferView];
+            const tinygltf::BufferView& outputView    = model.bufferViews[output.bufferView];
+
+            const tinygltf::Buffer& inputBuffer       = model.buffers[inputView.buffer];
+            const tinygltf::Buffer& outputBuffer      = model.buffers[outputView.buffer];
+
+            const float* timeStamps =
+                reinterpret_cast<const float*>(&inputBuffer.data[input.byteOffset + inputView.byteOffset]);
+            const float* values =
+                reinterpret_cast<const float*>(&outputBuffer.data[output.byteOffset + outputView.byteOffset]);
+
+            size_t keyframeCount        = input.count;
+
+            const std::string& nodeName = model.nodes[channel.target_node].name;
+            const uint32_t nameSize     = static_cast<uint32_t>(nodeName.size());
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&nameSize),
+                reinterpret_cast<const char*>(&nameSize) + sizeof(uint32_t)
+            );
+            buffer.insert(buffer.end(), nodeName.begin(), nodeName.end());
+
+            AnimationType animType = AnimationType::TRANSLATION;
+
+            if (channel.target_path == "rotation")
+            {
+                animType = AnimationType::ROTATION;
+            }
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&animType),
+                reinterpret_cast<const char*>(&animType) + sizeof(AnimationType)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(&keyframeCount),
+                reinterpret_cast<const char*>(&keyframeCount) + sizeof(uint32_t)
+            );
+
+            buffer.insert(
+                buffer.end(), reinterpret_cast<const char*>(timeStamps),
+                reinterpret_cast<const char*>(timeStamps) + keyframeCount * sizeof(float)
+            );
+
+            if (animType == AnimationType::TRANSLATION)
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(float3)
+                );
+            }
+            else
+            {
+                buffer.insert(
+                    buffer.end(), reinterpret_cast<const char*>(values),
+                    reinterpret_cast<const char*>(values) + keyframeCount * sizeof(Quat)
+                );
+            }
+        }
+
+        // Handle UID
+        const std::string fileName = FileSystem::GetFileNameWithoutExtension(sourceFilePath);
+        UID finalAnimUID;
+        if (sourceUID == INVALID_UID)
+        {
+            UID animationUID      = GenerateUID();
+            finalAnimUID          = App->GetLibraryModule()->AssignFiletypeUID(animationUID, FileType::Animation);
+
+            std::string assetPath = ANIMATIONS_PATH + FileSystem::GetFileNameWithExtension(sourceFilePath);
+            MetaAnimation meta(finalAnimUID, assetPath);
+
+            
+            
+            meta.Save(fileName, assetPath);
+        }
+        else
+        {
+            finalAnimUID = sourceUID;
+        }
+
+        // Construct save paths 
+
+        std::string saveFilePath = App->GetProjectModule()->GetLoadedProjectPath() + ANIMATIONS_PATH +
+                                   std::to_string(finalAnimUID) + ANIMATION_EXTENSION;
+      
+        unsigned int bytesWritten =
path: SobrassadaEngine/FileSystem/Importers/AnimationImporter.cpp
user: login: SirKaza
body: const
created_at: 2025-04-08T09:56:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+#pragma once
+
+#include "Globals.h"
+
+
+
+namespace tinygltf
+{
+    class Model;
+    class Animation;
+    class AnimationSampler;
path: SobrassadaEngine/FileSystem/Importers/AnimationImporter.h
user: login: SirKaza
body: This is a struct in tinygltf
created_at: 2025-04-08T10:03:37Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,30 @@
+#pragma once
+
+#include "Globals.h"
+
+
+
+namespace tinygltf
+{
+    class Model;
+    class Animation;
path: SobrassadaEngine/FileSystem/Importers/AnimationImporter.h
user: login: SirKaza
body: This is a struct in tinygltf
created_at: 2025-04-08T10:03:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -7,7 +7,9 @@
 #include "MetaModel.h"
 #include "Model.h"
 #include "ProjectModule.h"
-#include "ResourceModel.h"
+#include "ResourceManagement/Resources/ResourceModel.h"
path: SobrassadaEngine/FileSystem/Importers/ModelImporter.cpp
user: login: SirKaza
body: ResourceManagement/Resources/ not needed, take a look at includes directory of project
created_at: 2025-04-08T10:05:19Z
author_association: CONTRIBUTOR
diff_hunk: @@ -101,7 +103,52 @@ namespace ModelImporter
         assetPath = targetFilePath + assetPath;
 
         newModel.SetUID(finalModelUID);
+        const std::string animName = FileSystem::GetFileNameWithoutExtension(filePath);
+        std::string animAssetPath       = ANIMATIONS_ASSETS_PATH + animName + ANIMATION_EXTENSION;
path: SobrassadaEngine/FileSystem/Importers/ModelImporter.cpp
user: login: SirKaza
body: Not being used
created_at: 2025-04-08T10:05:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -101,7 +103,52 @@ namespace ModelImporter
         assetPath = targetFilePath + assetPath;
 
         newModel.SetUID(finalModelUID);
+        const std::string animName = FileSystem::GetFileNameWithoutExtension(filePath);
path: SobrassadaEngine/FileSystem/Importers/ModelImporter.cpp
user: login: SirKaza
body: Not being used
created_at: 2025-04-08T10:09:58Z
author_association: CONTRIBUTOR
diff_hunk: @@ -31,13 +31,19 @@ class Model
 
     const std::vector<std::vector<NodeData>>& GetNodes() const { return nodes; }
     const Skin& GetSkin(int skinIndex) const { return skins[skinIndex]; }
+    UID GetAnimationUID() const { return animUID; }
path: SobrassadaEngine/FileSystem/Model.h
user: login: SirKaza
body: const?
created_at: 2025-04-08T10:13:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,33 @@
+#pragma once
+#include "Resource.h"
+#include "Math/float3.h"
+#include "Math/Quat.h"
+#include <memory>
path: SobrassadaEngine/ResourceManagement/Resources/ResourceAnimation.h
user: login: SirKaza
body: memory not needed at least here
created_at: 2025-04-08T10:19:25Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,33 @@
+#pragma once
+#include "Resource.h"
+#include "Math/float3.h"
+#include "Math/Quat.h"
+#include <memory>
+#include <unordered_map>
+
+struct Channel
+{
+    std::vector<float3> positions;
+    std::vector<float> posTimeStamps;
+    std::vector<Quat> rotations;
+    std::vector<float> rotTimeStamps;
+    uint32_t numPositions = 0;
+    uint32_t numRotations = 0;
+};
+
+class ResourceAnimation : public Resource
+{
+  public:
+    std::unordered_map<std::string, Channel> channels;
path: SobrassadaEngine/ResourceManagement/Resources/ResourceAnimation.h
user: login: SirKaza
body: Public variables after and before private variables (don't merge methods with variables)
created_at: 2025-04-08T10:20:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,57 @@
+#include "ResourceAnimation.h"
+
+#include "Application.h"
+#include "DirectXTex/DirectXTex.h"
path: SobrassadaEngine/ResourceManagement/Resources/ResourceAnimation.cpp
user: login: SirKaza
body: directxtex and imgui in the bottom group
created_at: 2025-04-08T10:21:02Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,6 +23,7 @@ enum ComponentType
     COMPONENT_CANVAS,
     COMPONENT_LABEL,
     COMPONENT_CAMERA,
+    COMPONENT_ANIMATION,
path: SobrassadaEngine/Scene/Components/ComponentUtils.h
user: login: SirKaza
body: When adding new things, do it at the end, so current scenes are not broken
created_at: 2025-04-08T10:23:29Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+#pragma once
+#include "Globals.h"
+
+
+
+class ResourceAnimation;
+
+class AnimController
+{
+  public:
+   
+    AnimController();
+    ~AnimController();
+                                    
+    void GetTransform(const std::string& nodeName, float3& pos, Quat& rot); 
+
+    update_status Update(float deltaTime);  
+    void Play(UID resource, bool loop);
+    void Stop();
+    void Pause();
+    void Resume();
+    
+   ResourceAnimation* GetCurrentAnimation() { return animation; }
path: SobrassadaEngine/Scene/Components/Standalone/AnimController.h
user: login: SirKaza
body: getters first then setters
created_at: 2025-04-08T10:24:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+#pragma once
+#include "Globals.h"
+
+
+
+class ResourceAnimation;
+
+class AnimController
+{
+  public:
+   
+    AnimController();
+    ~AnimController();
+                                    
+    void GetTransform(const std::string& nodeName, float3& pos, Quat& rot); 
+
+    update_status Update(float deltaTime);  
+    void Play(UID resource, bool loop);
+    void Stop();
+    void Pause();
+    void Resume();
+    
+   ResourceAnimation* GetCurrentAnimation() { return animation; }
+    void SetAnimationResource(ResourceAnimation* anim) { animation = anim; }
+    void SetPlaybackSpeed(float speed) { playbackSpeed = speed; }
+    void SetTime(float time) { currentTime = time; }
+    float GetTime() const { return currentTime; }
+    bool IsPlaying() const { return playAnimation; }
+    
+  
+  
+   
+  private:
+    Quat Interpolate(Quat& first, Quat& second, float lambda);
path: SobrassadaEngine/Scene/Components/Standalone/AnimController.h
user: login: SirKaza
body: This private method separate it from variables
created_at: 2025-04-08T10:24:46Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,218 @@
+#include "AnimController.h"
path: SobrassadaEngine/Scene/Components/Standalone/AnimController.cpp
user: login: SirKaza
body: After the header of this cpp, separation line to the rest
created_at: 2025-04-08T10:25:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,218 @@
+#include "AnimController.h"
+#include "Application.h"
+#include "EngineTimer.h"
+#include "ResourceAnimation.h"
+#include "ResourcesModule.h"
+
+AnimController::AnimController()
+    : resource(0), currentTime(0), loop(false), playAnimation(false), playbackSpeed(1.0f), animation(nullptr)
+{
+}
+
+AnimController::~AnimController()
+{
+   
+    if (animation != nullptr)
path: SobrassadaEngine/Scene/Components/Standalone/AnimController.cpp
user: login: SirKaza
body: This if not needed, same with animation = nullptr right below
created_at: 2025-04-08T10:26:09Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,218 @@
+#include "AnimController.h"
+#include "Application.h"
+#include "EngineTimer.h"
+#include "ResourceAnimation.h"
+#include "ResourcesModule.h"
+
+AnimController::AnimController()
+    : resource(0), currentTime(0), loop(false), playAnimation(false), playbackSpeed(1.0f), animation(nullptr)
+{
+}
+
+AnimController::~AnimController()
+{
+   
+    if (animation != nullptr)
+    {
+        App->GetResourcesModule()->ReleaseResource(animation);
+        animation = nullptr;
+    }
+}
+
+void AnimController::Play(UID newResource, bool shouldLoop)
+{
+    resource      = newResource;
+    currentTime   = 0;
+    loop          = shouldLoop;
+    animation     = static_cast<ResourceAnimation*>(App->GetResourcesModule()->RequestResource(resource));
+    playAnimation = true;
+}
+
+void AnimController::Stop()
+{
+    playAnimation = false;
+    currentTime    = 0.0f;
+
+    if (animation != nullptr)
+    {
+        App->GetResourcesModule()->ReleaseResource(animation);
+        animation = nullptr;
+    }
+}
+
+void AnimController::Pause()
+{
+    playAnimation = false;
+}
+
+void AnimController::Resume()
+{
+    playAnimation = true;
+}
+
+Quat AnimController::Interpolate(Quat& first, Quat& second, float lambda)
+{
+    if (first.Dot(second) >= 0.0f) return Quat::Lerp(first, second, lambda).Normalized();
+    else return Quat::Lerp(first, second.Neg(), lambda).Normalized();
+}
+
+update_status AnimController::Update(float deltaTime)
+{
+    if (!playAnimation || resource == 0) return UPDATE_CONTINUE;
+
+    
+
+ 
+    deltaTime   *= playbackSpeed;
+
+    /*GLOG(
+        "Raw delta time: %f, Capped delta: %f, Playback speed: %f", App->GetEngineTimer()->GetTime(), rawDeltaTime,
+        playbackSpeed
+    );*/
+
+    float previousTime  = currentTime;
+    currentTime        += deltaTime;
+
+
+    if (animation == nullptr)
+    {
+        return UPDATE_CONTINUE;
+    }
+
+    const float duration = animation->GetDuration();
+    //GLOG("Animation time update: %f -> %f (duration: %f)", previousTime, currentTime, duration);
+
+    if (currentTime > duration)
+    {
+        if (loop)
+        {
+            currentTime = fmod(currentTime, duration);
+            //GLOG("Animation looped: new time = %f", currentTime);
+        }
+        else
+        {
+            currentTime = duration;
+            //GLOG("Animation reached end: time = %f", currentTime);
+            playAnimation = false;
+
+           if (animation != nullptr)
+            {
+                App->GetResourcesModule()->ReleaseResource(animation);
+                animation = nullptr;
+            }
+
+            return UPDATE_CONTINUE;
path: SobrassadaEngine/Scene/Components/Standalone/AnimController.cpp
user: login: SirKaza
body: This return is needed?
created_at: 2025-04-08T10:29:47Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,218 @@
+#include "AnimController.h"
+#include "Application.h"
+#include "EngineTimer.h"
+#include "ResourceAnimation.h"
+#include "ResourcesModule.h"
+
+AnimController::AnimController()
+    : resource(0), currentTime(0), loop(false), playAnimation(false), playbackSpeed(1.0f), animation(nullptr)
+{
+}
+
+AnimController::~AnimController()
+{
+   
+    if (animation != nullptr)
+    {
+        App->GetResourcesModule()->ReleaseResource(animation);
+        animation = nullptr;
+    }
+}
+
+void AnimController::Play(UID newResource, bool shouldLoop)
+{
+    resource      = newResource;
+    currentTime   = 0;
+    loop          = shouldLoop;
+    animation     = static_cast<ResourceAnimation*>(App->GetResourcesModule()->RequestResource(resource));
+    playAnimation = true;
+}
+
+void AnimController::Stop()
+{
+    playAnimation = false;
+    currentTime    = 0.0f;
+
+    if (animation != nullptr)
+    {
+        App->GetResourcesModule()->ReleaseResource(animation);
+        animation = nullptr;
+    }
+}
+
+void AnimController::Pause()
+{
+    playAnimation = false;
+}
+
+void AnimController::Resume()
+{
+    playAnimation = true;
+}
+
+Quat AnimController::Interpolate(Quat& first, Quat& second, float lambda)
+{
+    if (first.Dot(second) >= 0.0f) return Quat::Lerp(first, second, lambda).Normalized();
+    else return Quat::Lerp(first, second.Neg(), lambda).Normalized();
+}
+
+update_status AnimController::Update(float deltaTime)
+{
+    if (!playAnimation || resource == 0) return UPDATE_CONTINUE;
+
+    
+
+ 
+    deltaTime   *= playbackSpeed;
+
+    /*GLOG(
+        "Raw delta time: %f, Capped delta: %f, Playback speed: %f", App->GetEngineTimer()->GetTime(), rawDeltaTime,
+        playbackSpeed
+    );*/
+
+    float previousTime  = currentTime;
+    currentTime        += deltaTime;
+
+
+    if (animation == nullptr)
+    {
+        return UPDATE_CONTINUE;
+    }
+
+    const float duration = animation->GetDuration();
+    //GLOG("Animation time update: %f -> %f (duration: %f)", previousTime, currentTime, duration);
+
+    if (currentTime > duration)
+    {
+        if (loop)
+        {
+            currentTime = fmod(currentTime, duration);
+            //GLOG("Animation looped: new time = %f", currentTime);
+        }
+        else
+        {
+            currentTime = duration;
+            //GLOG("Animation reached end: time = %f", currentTime);
+            playAnimation = false;
+
+           if (animation != nullptr)
+            {
+                App->GetResourcesModule()->ReleaseResource(animation);
+                animation = nullptr;
+            }
+
+            return UPDATE_CONTINUE;
+        
+        }
+    }
+
+   
+    return UPDATE_CONTINUE;
+}
+
+void AnimController::GetTransform(const std::string& nodeName, float3& pos, Quat& rot)
+{
+    if (!playAnimation || resource == 0 || animation == nullptr) return;
+
+    Channel* animChannel = animation->GetChannel(nodeName);
+    if (animChannel == nullptr) return;
+
+    float animDuration = animation->GetDuration();
+    //GLOG("Animation duration: %f, Current time: %f", animDuration, currentTime);
+
+
+    if (animChannel->numPositions > 0)
+    {
+        if (animChannel->numPositions == 1)
+        {
+            pos = animChannel->positions[0];
+            //GLOG("Single position keyframe: (%f,%f,%f)", pos.x, pos.y, pos.z);
+        }
+        else
+        {
+            size_t nextIndex = 0;
+            while (nextIndex < animChannel->numPositions && animChannel->posTimeStamps[nextIndex] <= currentTime)
+            {
+                nextIndex++;
+            }
+
+            size_t prevIndex = (nextIndex > 0) ? nextIndex - 1 : 0;
+
+            if (nextIndex >= animChannel->numPositions)
+            {
+                pos = animChannel->positions[animChannel->numPositions - 1];
+                //GLOG("Past last position keyframe: (%f,%f,%f)", pos.x, pos.y, pos.z);
+            }
+            else if (nextIndex == 0)
+            {
+                pos = animChannel->positions[0];
+                //GLOG("Before first position keyframe: (%f,%f,%f)", pos.x, pos.y, pos.z);
+            }
+            else
+            {
+                float startTime = animChannel->posTimeStamps[prevIndex];
path: SobrassadaEngine/Scene/Components/Standalone/AnimController.cpp
user: login: SirKaza
body: this 3 variables can be const
created_at: 2025-04-08T10:30:54Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,218 @@
+#include "AnimController.h"
+#include "Application.h"
+#include "EngineTimer.h"
+#include "ResourceAnimation.h"
+#include "ResourcesModule.h"
+
+AnimController::AnimController()
+    : resource(0), currentTime(0), loop(false), playAnimation(false), playbackSpeed(1.0f), animation(nullptr)
+{
+}
+
+AnimController::~AnimController()
+{
+   
+    if (animation != nullptr)
+    {
+        App->GetResourcesModule()->ReleaseResource(animation);
+        animation = nullptr;
+    }
+}
+
+void AnimController::Play(UID newResource, bool shouldLoop)
+{
+    resource      = newResource;
+    currentTime   = 0;
+    loop          = shouldLoop;
+    animation     = static_cast<ResourceAnimation*>(App->GetResourcesModule()->RequestResource(resource));
+    playAnimation = true;
+}
+
+void AnimController::Stop()
+{
+    playAnimation = false;
+    currentTime    = 0.0f;
+
+    if (animation != nullptr)
+    {
+        App->GetResourcesModule()->ReleaseResource(animation);
+        animation = nullptr;
+    }
+}
+
+void AnimController::Pause()
+{
+    playAnimation = false;
+}
+
+void AnimController::Resume()
+{
+    playAnimation = true;
+}
+
+Quat AnimController::Interpolate(Quat& first, Quat& second, float lambda)
+{
+    if (first.Dot(second) >= 0.0f) return Quat::Lerp(first, second, lambda).Normalized();
+    else return Quat::Lerp(first, second.Neg(), lambda).Normalized();
+}
+
+update_status AnimController::Update(float deltaTime)
+{
+    if (!playAnimation || resource == 0) return UPDATE_CONTINUE;
+
+    
+
+ 
+    deltaTime   *= playbackSpeed;
+
+    /*GLOG(
+        "Raw delta time: %f, Capped delta: %f, Playback speed: %f", App->GetEngineTimer()->GetTime(), rawDeltaTime,
+        playbackSpeed
+    );*/
+
+    float previousTime  = currentTime;
+    currentTime        += deltaTime;
+
+
+    if (animation == nullptr)
+    {
+        return UPDATE_CONTINUE;
+    }
+
+    const float duration = animation->GetDuration();
+    //GLOG("Animation time update: %f -> %f (duration: %f)", previousTime, currentTime, duration);
+
+    if (currentTime > duration)
+    {
+        if (loop)
+        {
+            currentTime = fmod(currentTime, duration);
+            //GLOG("Animation looped: new time = %f", currentTime);
+        }
+        else
+        {
+            currentTime = duration;
+            //GLOG("Animation reached end: time = %f", currentTime);
+            playAnimation = false;
+
+           if (animation != nullptr)
+            {
+                App->GetResourcesModule()->ReleaseResource(animation);
+                animation = nullptr;
+            }
+
+            return UPDATE_CONTINUE;
+        
+        }
+    }
+
+   
+    return UPDATE_CONTINUE;
+}
+
+void AnimController::GetTransform(const std::string& nodeName, float3& pos, Quat& rot)
+{
+    if (!playAnimation || resource == 0 || animation == nullptr) return;
+
+    Channel* animChannel = animation->GetChannel(nodeName);
+    if (animChannel == nullptr) return;
+
+    float animDuration = animation->GetDuration();
+    //GLOG("Animation duration: %f, Current time: %f", animDuration, currentTime);
+
+
+    if (animChannel->numPositions > 0)
+    {
+        if (animChannel->numPositions == 1)
+        {
+            pos = animChannel->positions[0];
+            //GLOG("Single position keyframe: (%f,%f,%f)", pos.x, pos.y, pos.z);
+        }
+        else
+        {
+            size_t nextIndex = 0;
+            while (nextIndex < animChannel->numPositions && animChannel->posTimeStamps[nextIndex] <= currentTime)
+            {
+                nextIndex++;
+            }
+
+            size_t prevIndex = (nextIndex > 0) ? nextIndex - 1 : 0;
+
+            if (nextIndex >= animChannel->numPositions)
+            {
+                pos = animChannel->positions[animChannel->numPositions - 1];
+                //GLOG("Past last position keyframe: (%f,%f,%f)", pos.x, pos.y, pos.z);
+            }
+            else if (nextIndex == 0)
+            {
+                pos = animChannel->positions[0];
+                //GLOG("Before first position keyframe: (%f,%f,%f)", pos.x, pos.y, pos.z);
+            }
+            else
+            {
+                float startTime = animChannel->posTimeStamps[prevIndex];
+                float endTime   = animChannel->posTimeStamps[nextIndex];
+                float timeDiff  = endTime - startTime;
+
+                float lambda    = (timeDiff > 0.0001f) ? (currentTime - startTime) / timeDiff : 0.0f;
+
+                lambda          = (lambda < 0) ? 0 : (lambda > 1) ? 1 : lambda;
+
+                pos = float3::Lerp(animChannel->positions[prevIndex], animChannel->positions[nextIndex], lambda);
+                /*GLOG(
+                    "Position interpolation: (%f,%f,%f), Lambda: %f, Times: %f to %f", pos.x, pos.y, pos.z, lambda,
+                    startTime, endTime
+                );*/
+            }
+        }
+    }
+
+    if (animChannel->numRotations > 0)
+    {
+        if (animChannel->numRotations == 1)
+        {
+            rot = animChannel->rotations[0].Normalized();
+            GLOG("Single rotation keyframe: (%f,%f,%f,%f)", rot.x, rot.y, rot.z, rot.w);
+        }
+        else
+        {
+            size_t nextIndex = 0;
+            while (nextIndex < animChannel->numRotations && animChannel->rotTimeStamps[nextIndex] <= currentTime)
+            {
+                nextIndex++;
+            }
+
+            size_t prevIndex = (nextIndex > 0) ? nextIndex - 1 : 0;
+
+            if (nextIndex >= animChannel->numRotations)
+            {
+                rot = animChannel->rotations[animChannel->numRotations - 1].Normalized();
+                //GLOG("Past last rotation keyframe: (%f,%f,%f,%f)", rot.x, rot.y, rot.z, rot.w);
+            }
+            else if (nextIndex == 0)
+            {
+                rot = animChannel->rotations[0].Normalized();
+                //GLOG("Before first rotation keyframe: (%f,%f,%f,%f)", rot.x, rot.y, rot.z, rot.w);
+            }
+            else
+            {
+                float startTime = animChannel->rotTimeStamps[prevIndex];
path: SobrassadaEngine/Scene/Components/Standalone/AnimController.cpp
user: login: SirKaza
body: same here
created_at: 2025-04-08T10:31:28Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+#pragma once
+
+#include "Globals.h"
+#include "Scene/Components/Component.h"
+#include "AnimController.h"
+#include <Libs/rapidjson/document.h>
+#include <cstdint>
+#include <unordered_map>
+
+class AnimationComponent : public Component
+{
+  public:
+    AnimationComponent(UID uid, GameObject* parent);
+    AnimationComponent(const rapidjson::Value& initialState, GameObject* parent);
+    
+    ~AnimationComponent();
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h
user: login: SirKaza
body: override
created_at: 2025-04-08T10:32:38Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+#pragma once
+
+#include "Globals.h"
+#include "Scene/Components/Component.h"
+#include "AnimController.h"
+#include <Libs/rapidjson/document.h>
+#include <cstdint>
+#include <unordered_map>
+
+class AnimationComponent : public Component
+{
+  public:
+    AnimationComponent(UID uid, GameObject* parent);
+    AnimationComponent(const rapidjson::Value& initialState, GameObject* parent);
+    
+    ~AnimationComponent();
+    void OnPlay();
+    void OnStop();
+    void OnPause();
+    void OnResume();
+    void OnInspector();
+
+    void Clone(const Component* other) override;
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h
user: login: SirKaza
body: constructors -> override -> normal methogs -> getters and setters
created_at: 2025-04-08T10:33:35Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+#pragma once
+
+#include "Globals.h"
+#include "Scene/Components/Component.h"
+#include "AnimController.h"
+#include <Libs/rapidjson/document.h>
+#include <cstdint>
+#include <unordered_map>
+
+class AnimationComponent : public Component
+{
+  public:
+    AnimationComponent(UID uid, GameObject* parent);
+    AnimationComponent(const rapidjson::Value& initialState, GameObject* parent);
+    
+    ~AnimationComponent();
+    void OnPlay();
+    void OnStop();
+    void OnPause();
+    void OnResume();
+    void OnInspector();
+
+    void Clone(const Component* other) override;
+    void Update(float deltaTime) override;
+    void Render(float deltaTime) override;
+    void RenderEditorInspector() override;
+  
+
+    void Save(rapidjson::Value& targetState, rapidjson::Document::AllocatorType& allocator) const override;
+    void AddAnimation(UID resource);
+    UID GetAnimationResource() const { GLOG("Resource AnimUID  is: %d",resource) return resource; }
+    void SetAnimationResource(UID animResource);
+    ResourceAnimation* GetCurrentAnimation() const { return currentAnimResource; }
+    AnimController* GetAnimationController() { return animController; }
+    std::unordered_map<std::string, GameObject*> GetBoneMapping() const { return boneMapping; }
+
+  private:
+    void SetBoneMapping();
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h
user: login: SirKaza
body: method outside variables defined
created_at: 2025-04-08T10:34:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+#pragma once
+
+#include "Globals.h"
+#include "Scene/Components/Component.h"
+#include "AnimController.h"
+#include <Libs/rapidjson/document.h>
+#include <cstdint>
+#include <unordered_map>
+
+class AnimationComponent : public Component
+{
+  public:
+    AnimationComponent(UID uid, GameObject* parent);
+    AnimationComponent(const rapidjson::Value& initialState, GameObject* parent);
+    
+    ~AnimationComponent();
+    void OnPlay();
+    void OnStop();
+    void OnPause();
+    void OnResume();
+    void OnInspector();
+
+    void Clone(const Component* other) override;
+    void Update(float deltaTime) override;
+    void Render(float deltaTime) override;
+    void RenderEditorInspector() override;
+  
+
+    void Save(rapidjson::Value& targetState, rapidjson::Document::AllocatorType& allocator) const override;
+    void AddAnimation(UID resource);
+    UID GetAnimationResource() const { GLOG("Resource AnimUID  is: %d",resource) return resource; }
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h
user: login: SirKaza
body: Not sure if this should be inline with the GLOG cause its not one line
created_at: 2025-04-08T10:35:33Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+#pragma once
+
+#include "Globals.h"
+#include "Scene/Components/Component.h"
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h
user: login: SirKaza
body: #include "Component"
created_at: 2025-04-08T10:36:13Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+#pragma once
+
+#include "Globals.h"
+#include "Scene/Components/Component.h"
+#include "AnimController.h"
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h
user: login: SirKaza
body: Forward declaration of GameObject, AnimController, AnimationComponent and ResourceAnimation. All variables that are pointers or reference can be forward declared if not methods of these classes used in header
created_at: 2025-04-08T10:36:27Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,52 @@
+#pragma once
+
+#include "Globals.h"
+#include "Scene/Components/Component.h"
+#include "AnimController.h"
+#include <Libs/rapidjson/document.h>
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.h
user: login: SirKaza
body: #include "rapidjson/document.h"
created_at: 2025-04-08T10:36:39Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,389 @@
+#include "AnimationComponent.h"
+#include "ResourceManagement/Resources/ResourceAnimation.h"
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.cpp
user: login: SirKaza
body: No need of ResourceManagement/Resources in all the resources includes, there is 2 more below
created_at: 2025-04-08T10:39:17Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,389 @@
+#include "AnimationComponent.h"
+#include "ResourceManagement/Resources/ResourceAnimation.h"
+#include "ResourceManagement/Resources/Resource.h"
+#include "GameObject.h"
+#include "Application.h"
+#include "CameraModule.h"
+#include "EditorUIModule.h"
+#include "LibraryModule.h"
+#include "ResourcesModule.h"
+#include "SceneModule.h"
+
+#include "imgui.h"
+#include <Math/Quat.h>
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.cpp
user: login: SirKaza
body: ""
created_at: 2025-04-08T10:39:26Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,389 @@
+#include "AnimationComponent.h"
+#include "ResourceManagement/Resources/ResourceAnimation.h"
+#include "ResourceManagement/Resources/Resource.h"
+#include "GameObject.h"
+#include "Application.h"
+#include "CameraModule.h"
+#include "EditorUIModule.h"
+#include "LibraryModule.h"
+#include "ResourcesModule.h"
+#include "SceneModule.h"
+
+#include "imgui.h"
+#include <Math/Quat.h>
+
+AnimationComponent::AnimationComponent(
+    const UID uid, GameObject* parent
+)
+    : Component(uid, parent, "Animation", COMPONENT_ANIMATION)
+{
+    animController = new AnimController();
+  
+}
+
+AnimationComponent::AnimationComponent(const rapidjson::Value& initialState, GameObject* parent)
+    : Component(initialState, parent )
+{
+    animController = new AnimController();
+    if (initialState.HasMember("Animations") && initialState["Animations"].IsUint64())
+    {
+        resource = initialState["Animations"].GetUint64();
+    }
+    else
+    {
+        resource = 0;
+    }
+}
+
+AnimationComponent::~AnimationComponent()
+{
+    if (animController != nullptr)
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.cpp
user: login: SirKaza
body: no need of if and = nullptr
created_at: 2025-04-08T10:40:12Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,389 @@
+#include "AnimationComponent.h"
+#include "ResourceManagement/Resources/ResourceAnimation.h"
+#include "ResourceManagement/Resources/Resource.h"
+#include "GameObject.h"
+#include "Application.h"
+#include "CameraModule.h"
+#include "EditorUIModule.h"
+#include "LibraryModule.h"
+#include "ResourcesModule.h"
+#include "SceneModule.h"
+
+#include "imgui.h"
+#include <Math/Quat.h>
+
+AnimationComponent::AnimationComponent(
+    const UID uid, GameObject* parent
+)
+    : Component(uid, parent, "Animation", COMPONENT_ANIMATION)
+{
+    animController = new AnimController();
+  
+}
+
+AnimationComponent::AnimationComponent(const rapidjson::Value& initialState, GameObject* parent)
+    : Component(initialState, parent )
+{
+    animController = new AnimController();
+    if (initialState.HasMember("Animations") && initialState["Animations"].IsUint64())
+    {
+        resource = initialState["Animations"].GetUint64();
+    }
+    else
+    {
+        resource = 0;
+    }
+}
+
+AnimationComponent::~AnimationComponent()
+{
+    if (animController != nullptr)
+    {
+        delete animController;
+        animController = nullptr;
+    }
+    App->GetResourcesModule()->ReleaseResource(currentAnimResource);
+}
+
+void AnimationComponent::OnPlay()
+{
+    if (animController != nullptr && resource != 0)
+    {
+        animController->Play(resource, true);
path: SobrassadaEngine/Scene/Components/Standalone/AnimationComponent.cpp
user: login: SirKaza
body: All these if until OnResume can be one line IF
created_at: 2025-04-08T10:41:31Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,41 @@
+#pragma once
+#include "Globals.h"
+
+
+
+class ResourceAnimation;
+
+class AnimController
+{
+  public:
+   
+    AnimController();
+    ~AnimController();
+                                    
+    void GetTransform(const std::string& nodeName, float3& pos, Quat& rot); 
+
+    update_status Update(float deltaTime);  
+    void Play(UID resource, bool loop);
+    void Stop();
+    void Pause();
path: SobrassadaEngine/Scene/Components/Standalone/AnimController.h
user: login: SirKaza
body: Pause and resume do it here instead of cpp, they are one line
created_at: 2025-04-08T10:42:44Z
author_association: CONTRIBUTOR
diff_hunk: @@ -23,6 +23,9 @@
 #include "ResourceModel.h"
 #include "ResourcePrefab.h"
 #include "ResourcesModule.h"
+#include "Scene/Components/ComponentUtils.h"
path: SobrassadaEngine/Scene/Scene.cpp
user: login: SirKaza
body: no need of Scene/Components here and 2 below
created_at: 2025-04-08T10:46:01Z
author_association: CONTRIBUTOR
diff_hunk: @@ -286,6 +286,8 @@
   </ItemDefinitionGroup>
   <ItemGroup>
     <ClCompile Include="Application.cpp" />
+    <ClCompile Include="FileSystem\Importers\AnimationImporter.cpp" />
path: SobrassadaEngine/SobrassadaEngine.vcxproj
user: login: SirKaza
body: MetaAnimation.h and AnimationImporter.h missing
created_at: 2025-04-08T10:48:36Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,23 @@
+#pragma once
+#include "EngineEditorBase.h"
+class GameObject;
+class ResourceAnimation;
+class AnimationComponent;
+class AnimatorEditor : public EngineEditorBase
+{
+  public:
+    AnimatorEditor(const std::string& editorName, const UID uid);
+    ~AnimatorEditor();
path: SobrassadaEngine/UI/EngineEditors/Editor/AnimatorEditor.h
user: login: SirKaza
body: override
created_at: 2025-04-08T10:49:42Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,23 @@
+#pragma once
+#include "EngineEditorBase.h"
+class GameObject;
+class ResourceAnimation;
+class AnimationComponent;
+class AnimatorEditor : public EngineEditorBase
path: SobrassadaEngine/UI/EngineEditors/Editor/AnimatorEditor.h
user: login: SirKaza
body: If this is deleted, delete the files
created_at: 2025-04-08T10:52:18Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,190 @@
+#include "AnimController.h"
+
+#include "Application.h"
+#include "EngineTimer.h"
+#include "ResourceAnimation.h"
+#include "ResourcesModule.h"
+
+AnimController::AnimController()
+    : resource(0), currentTime(0), loop(false), playAnimation(false), playbackSpeed(1.0f), animation(nullptr)
+{
+}
+
+AnimController::~AnimController()
+{
+    App->GetResourcesModule()->ReleaseResource(animation);
+}
+
+void AnimController::Play(UID newResource, bool shouldLoop)
+{
+    resource      = newResource;
+    currentTime   = 0;
+    loop          = shouldLoop;
+    animation     = static_cast<ResourceAnimation*>(App->GetResourcesModule()->RequestResource(resource));
+    playAnimation = true;
+}
+
+void AnimController::Stop()
+{
+    playAnimation = false;
+    currentTime   = 0.0f;
+
+    if (animation != nullptr)
+    {
+        App->GetResourcesModule()->ReleaseResource(animation);
+        animation = nullptr;
+    }
+}
+
+Quat AnimController::Interpolate(Quat& first, Quat& second, float lambda)
path: SobrassadaEngine/Scene/Components/Standalone/AnimController.cpp
user: login: IDarkgenesis
body: Both Quat can be const and the function aswell
created_at: 2025-04-10T23:34:48Z
author_association: CONTRIBUTOR

PR comments:
user: login: PabioGF
created_at: 2025-04-07T17:58:50Z
body: Shouldn't be merged into VS2 instead of main?
author_association: CONTRIBUTOR
user: login: DHazardFenrir
created_at: 2025-04-07T18:02:39Z
body: > Shouldn't be merged into VS2 instead of main?

Its already merge with the stuff in VS2 not main 
author_association: CONTRIBUTOR
user: login: DHazardFenrir
created_at: 2025-04-08T15:11:16Z
body: I addressed all the changes requested. It should work now, even if the model doesn't have animations (there is an if that if the model doesnt have it doesn't apply the resourceModel->SetAllAnimations and SetAnimation). I also moved all asset files to the engineTests repo. 

Now for testing is moving the zombunny or player to assets, import it, load it, select the root gameObject and then press play and stop. 

The thing is, github told me I still had conflicts with vs2slice branch and that I needed to do some merge. I did it, but now my project isn't compiling because I have this linking error 'cannot open input file 'SobrassadaEngine.lib'
author_association: CONTRIBUTOR
user: login: DHazardFenrir
created_at: 2025-04-08T16:54:24Z
body: I ran it in release, but I had to rebuild only the sobrasadaScripts project, since if I tried to do a full rebuild the error I mentioned popped up
author_association: CONTRIBUTOR
user: login: DHazardFenrir
created_at: 2025-04-09T16:59:27Z
body: I checked and it says the branch is now up to date with vs2slice and main. 
author_association: CONTRIBUTOR
user: login: srLlobet
created_at: 2025-04-09T21:19:12Z
body: Are the instructions old? I tried with the player, zombunny and a random animated wolf i found online, but after making a new scene, importing the gltfs, I add the animation component and there are no avaliable animations and nothing to do?
author_association: CONTRIBUTOR
user: login: DHazardFenrir
created_at: 2025-04-10T14:50:20Z
body: > Are the instructions old? I tried with the player, zombunny and a random animated wolf i found online, but after making a new scene, importing the gltfs, I add the animation component and there are no avaliable animations and nothing to do?

I said that if you add the component manually its it not going to work. The animation component should appear on the root when you load the models. I tested it on release and imported and loaded the zombunny again and it shows the animation component on the gameobject root. 
author_association: CONTRIBUTOR