diff --git a/Editor/resources/Shaders/flat.shader b/Editor/resources/Shaders/flat.shader index c191336d..8f6cb3d8 100644 --- a/Editor/resources/Shaders/flat.shader +++ b/Editor/resources/Shaders/flat.shader @@ -41,5 +41,5 @@ void main() vec3 diffuse = diff * lightDiffuse; scolor = scolor * diffuse; - FragColor = vec4(scolor, 1.0); + FragColor = vec4(u_Color.rgb, 1.0); } \ No newline at end of file diff --git a/Editor/resources/Shaders/gbuffer_skinned.shader b/Editor/resources/Shaders/gbuffer_skinned.shader index c13922ce..4712d27d 100644 --- a/Editor/resources/Shaders/gbuffer_skinned.shader +++ b/Editor/resources/Shaders/gbuffer_skinned.shader @@ -13,7 +13,7 @@ uniform mat4 u_Model; uniform mat4 u_View; uniform mat4 u_Projection; -const int MAX_BONES = 100; +const int MAX_BONES = 200; const int MAX_BONES_INFLUENCE = 4; uniform mat4 u_FinalBonesMatrice[MAX_BONES]; diff --git a/Editor/resources/Shaders/gizmo.shader b/Editor/resources/Shaders/gizmo.shader index db6cb6ef..2d4bb2ea 100644 --- a/Editor/resources/Shaders/gizmo.shader +++ b/Editor/resources/Shaders/gizmo.shader @@ -1,18 +1,19 @@ #shader vertex #version 460 core layout(location = 0) in vec3 Position; +layout(location = 1) in vec3 Normal; layout(location = 1) in vec2 UV; -uniform mat4 model; -uniform mat4 view; -uniform mat4 projection; +uniform mat4 u_Model; +uniform mat4 u_View; +uniform mat4 u_Projection; out sample vec2 a_UV; void main() { a_UV = UV; - gl_Position = projection * view * model * vec4(Position, 1.0); + gl_Position = u_Projection * u_View * u_Model * vec4(Position, 1.0); } #shader fragment @@ -27,5 +28,6 @@ uniform sampler2D gizmo_texture; void main() { vec4 px_color = texture(gizmo_texture, a_UV).rgba; - FragColor = px_color; + FragColor = px_color * vec4(1, 1, 1, 0.5); + } \ No newline at end of file diff --git a/Editor/src/ComponentsPanel/BonePanel.h b/Editor/src/ComponentsPanel/BonePanel.h new file mode 100644 index 00000000..cd80a554 --- /dev/null +++ b/Editor/src/ComponentsPanel/BonePanel.h @@ -0,0 +1,36 @@ +#pragma once +#include "ComponentPanel.h" + +#include +#include +#include +#include + +class BonePanel : ComponentPanel +{ +public: + BonePanel() {} + + void Draw(Nuake::Entity entity) override + { + using namespace Nuake; + + if (!entity.HasComponent()) + return; + + auto& component = entity.GetComponent(); + BeginComponentTable(BONE, BoneComponent); + { + { + ImGui::Text("Name"); + ImGui::TableNextColumn(); + + ImGui::InputText("##BoneName", &component.Name); + ImGui::TableNextColumn(); + + ComponentTableReset(component.Name, ""); + } + } + EndComponentTable(); + } +}; \ No newline at end of file diff --git a/Editor/src/ComponentsPanel/SkinnedModelPanel.h b/Editor/src/ComponentsPanel/SkinnedModelPanel.h index 9bebded1..cdba235d 100644 --- a/Editor/src/ComponentsPanel/SkinnedModelPanel.h +++ b/Editor/src/ComponentsPanel/SkinnedModelPanel.h @@ -12,8 +12,9 @@ class SkinnedModelPanel : ComponentPanel { private: - Scope _modelInspector; - bool _expanded = false; + Scope m_ModelInspector; + bool m_Expanded = false; + std::string m_QueuedModelPath; public: SkinnedModelPanel() @@ -45,9 +46,9 @@ public: { } - if (_expanded) + if (m_Expanded) { - _modelInspector->Draw(); + m_ModelInspector->Draw(); } if (ImGui::BeginDragDropTarget()) @@ -64,15 +65,107 @@ public: } else { - component.ModelPath = fullPath; - component.LoadModel(); + m_QueuedModelPath = fullPath; + ImGui::OpenPopup("Create Skeleton"); } } ImGui::EndDragDropTarget(); } + if (ImGui::BeginPopupModal("Create Skeleton", NULL, ImGuiWindowFlags_AlwaysAutoResize)) + { + ImGui::SetItemDefaultFocus(); + ImGui::Text("Would you like to create the skeleton structure in the scene tree?"); + ImGui::Separator(); + + if (ImGui::Button("OK", ImVec2(120, 0))) + { + component.ModelPath = m_QueuedModelPath; + component.LoadModel(); + + Scene* scene = entity.GetScene(); + scene->CreateSkeleton(entity); + + ImGui::CloseCurrentPopup(); + } + + ImGui::SetItemDefaultFocus(); + ImGui::SameLine(); + + if (ImGui::Button("Cancel", ImVec2(120, 0))) + { + ImGui::CloseCurrentPopup(); + } + + ImGui::EndPopup(); + } + ImGui::TableNextColumn(); ComponentTableReset(component.ModelPath, ""); + + if (component.ModelResource) + { + auto& model = component.ModelResource; + ImGui::TableNextColumn(); + + { + ImGui::Text("Playing"); + ImGui::TableNextColumn(); + + ImGui::Checkbox("##playing", &model->IsPlaying); + + ImGui::TableNextColumn(); + ComponentTableReset(model->IsPlaying, true); + ImGui::TableNextColumn(); + } + + if(model->GetCurrentAnimation()) + { + ImGui::Text("Animation"); + ImGui::TableNextColumn(); + + uint32_t animIndex = model->GetCurrentAnimationIndex(); + uint32_t oldAnimIndex = animIndex; + auto& animations = model->GetAnimations(); + if (ImGui::BeginCombo("Type", model->GetCurrentAnimation()->GetName().c_str())) + { + for (int n = 0; n < model->GetAnimationsCount(); n++) + { + bool is_selected = (animIndex == n); + std::string animName = animations[n]->GetName(); + + if (animName.empty()) + { + animName = "Empty"; + } + + if (ImGui::Selectable(animName.c_str(), is_selected)) + { + animIndex = n; + } + + if (is_selected) + ImGui::SetItemDefaultFocus(); + } + ImGui::EndCombo(); + } + + if (animIndex != oldAnimIndex) + { + model->PlayAnimation(animIndex); + } + + ImGui::TableNextColumn(); + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + std::string resetLabel = std::string(ICON_FA_UNDO) + "##ResetAnimId"; + if (ImGui::Button(resetLabel.c_str())) + { + model->PlayAnimation(0); + } + ImGui::PopStyleColor(); + } + } + } EndComponentTable(); } diff --git a/Editor/src/Misc/GizmoDrawer.cpp b/Editor/src/Misc/GizmoDrawer.cpp index 77ee319b..c883d3de 100644 --- a/Editor/src/Misc/GizmoDrawer.cpp +++ b/Editor/src/Misc/GizmoDrawer.cpp @@ -9,6 +9,7 @@ #include #include +#include #include @@ -17,7 +18,7 @@ #include #include #include - +#include GizmoDrawer::GizmoDrawer() { @@ -275,69 +276,98 @@ void GizmoDrawer::DrawGizmos(Ref scene) glLineWidth(1.0f); RenderList renderList; + auto gizmoShader = ShaderManager::GetShader("resources/Shaders/gizmo.shader"); + gizmoShader->Bind(); + gizmoShader->SetUniformMat4f("u_View", scene->m_EditorCamera->GetTransform()); + gizmoShader->SetUniformMat4f("u_Projection", scene->m_EditorCamera->GetPerspective()); + + RenderCommand::Disable(RendererEnum::FACE_CULL); + // Camera auto camView = scene->m_Registry.view(); for (auto e : camView) { - auto& [transform, cam] = scene->m_Registry.get(e); + gizmoShader->SetUniformTex("gizmo_texture", TextureManager::Get()->GetTexture("resources/Gizmos/camera.png").get()); + auto [transform, camera] = scene->m_Registry.get(e); - renderList.AddToRenderList(_gizmos["cam"]->GetMeshes()[0], transform.GetGlobalTransform()); + auto initialTransform = transform.GetGlobalTransform(); + Matrix4 particleTransform = initialTransform; + particleTransform = glm::inverse(scene->m_EditorCamera->GetTransform()); - auto view = transform.GetGlobalTransform(); - Frustum& frustum = cam.CameraInstance->GetFrustum(); - auto& frustumCorners = frustum.GetPoints(); + // Translation + const Vector3& particleGlobalPosition = transform.GetGlobalPosition(); + particleTransform[3] = initialTransform[3]; - constexpr int frustumEdges[12][2] = { - {0, 1}, {1, 3}, {3, 2}, {2, 0}, // Near plane edges - {4, 5}, {5, 7}, {7, 6}, {6, 4}, // Far plane edges - {0, 4}, {1, 5}, {2, 6}, {3, 7} // Connection lines - }; - - glBegin(GL_LINES); - - for (int i = 0; i < 12; ++i) { - int startIdx = frustumEdges[i][0]; - int endIdx = frustumEdges[i][1]; - const Vector3& startCorner = view * Vector4(frustumCorners[startIdx], 1.0f); - const Vector3& endCorner = frustumCorners[endIdx]; - glVertex3f(startCorner.x, startCorner.y, startCorner.z); - glVertex3f(endCorner.x, endCorner.y, endCorner.z); - } - - glEnd(); + particleTransform = glm::scale(particleTransform, Vector3(0.5, 0.5, 0.5)); + renderList.AddToRenderList(Renderer::QuadMesh, particleTransform); } - - renderList.Flush(flatShader, true); + renderList.Flush(gizmoShader, true); // Lights auto lightView = scene->m_Registry.view(); for (auto e : lightView) { + gizmoShader->SetUniformTex("gizmo_texture", TextureManager::Get()->GetTexture("resources/Gizmos/light.png").get()); auto [transform, light] = scene->m_Registry.get(e); - flatShader->SetUniformVec4("u_Color", Vector4(light.Color, 1.0f)); - renderList.AddToRenderList(_gizmos["light"]->GetMeshes()[0], transform.GetGlobalTransform()); - renderList.Flush(flatShader, true); - } - renderList.Flush(flatShader, true); + auto initialTransform = transform.GetGlobalTransform(); + Matrix4 particleTransform = initialTransform; + particleTransform = glm::inverse(scene->m_EditorCamera->GetTransform()); + // Translation + const Vector3& particleGlobalPosition = transform.GetGlobalPosition(); + particleTransform[3] = initialTransform[3]; + + particleTransform = glm::scale(particleTransform, Vector3(0.5, 0.5, 0.5)); + + renderList.AddToRenderList(Renderer::QuadMesh, particleTransform); + } + + renderList.Flush(gizmoShader, true); // Player auto characterControllerView = scene->m_Registry.view(); for (auto e : characterControllerView) { - auto [transformComponent, characterControllerComponent] = scene->m_Registry.get(e); + gizmoShader->SetUniformTex("gizmo_texture", TextureManager::Get()->GetTexture("resources/Gizmos/player.png").get()); + auto [transform, characterControllerComponent] = scene->m_Registry.get(e); - flatShader->SetUniformVec4("u_Color", Vector4(0.0f, 1.0f, 0.4f, 1.0f)); + auto initialTransform = transform.GetGlobalTransform(); + Matrix4 particleTransform = initialTransform; + particleTransform = glm::inverse(scene->m_EditorCamera->GetTransform()); - const auto scaledTransform = glm::scale(transformComponent.GetGlobalTransform(), Vector3(0.25f, 0.25f, 0.25f)); - renderList.AddToRenderList(_gizmos["player"]->GetMeshes()[0], scaledTransform); - renderList.Flush(flatShader, true); + // Translation + const Vector3& particleGlobalPosition = transform.GetGlobalPosition(); + particleTransform[3] = initialTransform[3]; + + particleTransform = glm::scale(particleTransform, Vector3(0.5, 0.5, 0.5)); + + renderList.AddToRenderList(Renderer::QuadMesh, particleTransform); } - renderList.Flush(flatShader, true); + renderList.Flush(gizmoShader, true); - RenderCommand::Disable(RendererEnum::FACE_CULL); + // Bones + auto boneView = scene->m_Registry.view(); + for (auto e : boneView) + { + gizmoShader->SetUniformTex("gizmo_texture", TextureManager::Get()->GetTexture("resources/Gizmos/bone.png").get()); + auto [transform, boneComponent] = scene->m_Registry.get(e); + + auto initialTransform = transform.GetGlobalTransform(); + Matrix4 particleTransform = initialTransform; + particleTransform = glm::inverse(scene->m_EditorCamera->GetTransform()); + + // Translation + const Vector3& particleGlobalPosition = transform.GetGlobalPosition(); + particleTransform[3] = initialTransform[3]; + particleTransform = glm::scale(particleTransform, Vector3(0.1, 0.1, 0.1)); + + renderList.AddToRenderList(Renderer::QuadMesh, particleTransform); + } + + renderList.Flush(gizmoShader, true); + RenderCommand::Enable(RendererEnum::DEPTH_TEST); } \ No newline at end of file diff --git a/Editor/src/Windows/EditorInterface.cpp b/Editor/src/Windows/EditorInterface.cpp index d6acdcb4..0196ef75 100644 --- a/Editor/src/Windows/EditorInterface.cpp +++ b/Editor/src/Windows/EditorInterface.cpp @@ -252,7 +252,7 @@ namespace Nuake { m_IsViewportFocused = ImGui::IsWindowFocused(); - if (m_IsHoveringViewport && Input::IsMouseButtonPressed(GLFW_MOUSE_BUTTON_1) && !ImGuizmo::IsUsing()) + if (m_IsHoveringViewport && Input::IsMouseButtonPressed(GLFW_MOUSE_BUTTON_1) && !ImGuizmo::IsUsing() && m_IsViewportFocused) { const auto windowPosNuake = Vector2(windowPos.x, windowPos.y); auto& gbuffer = Engine::GetCurrentScene()->m_SceneRenderer->GetGBuffer(); @@ -289,7 +289,9 @@ namespace Nuake { Entity QueueDeletion; void EditorInterface::DrawEntityTree(Entity e) { - ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_FramePadding | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanAvailWidth; + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{ 0.0f, 0.0f }); + + ImGuiTreeNodeFlags base_flags = ImGuiTreeNodeFlags_OpenOnArrow | ImGuiTreeNodeFlags_FramePadding | ImGuiTreeNodeFlags_OpenOnDoubleClick | ImGuiTreeNodeFlags_SpanFullWidth; NameComponent& nameComponent = e.GetComponent(); std::string name = nameComponent.Name; @@ -298,8 +300,6 @@ namespace Nuake { if (Selection.Type == EditorSelectionType::Entity && Selection.Entity == e) base_flags |= ImGuiTreeNodeFlags_Selected; - - ImGui::TableNextColumn(); // Write in normal font. @@ -315,10 +315,8 @@ namespace Nuake { { ImGui::PushStyleColor(ImGuiCol_Text, IM_COL32(0, 255, 0, 255)); } - - ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(8, 8)); + bool open = ImGui::TreeNodeEx(name.c_str(), base_flags); - ImGui::PopStyleVar(); if(nameComponent.IsPrefab && e.HasComponent()) ImGui::PopStyleColor(); @@ -414,21 +412,20 @@ namespace Nuake { ImGui::TableNextColumn(); - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{ 0.0f, 0.0f }); + ImGui::TextColored(ImVec4(0.5, 0.5, 0.5, 1.0), GetEntityTypeName(e).c_str()); + + ImGui::TableNextColumn(); { bool& isVisible = e.GetComponent().Visible; char* visibilityIcon = isVisible ? ICON_FA_EYE : ICON_FA_EYE_SLASH; - ImGui::PushStyleColor(ImGuiCol_Button, { 0, 0, 0, 0 }); - if (ImGui::Button(visibilityIcon, { 40, 36 })) + if (ImGui::Button(visibilityIcon, { 40, 0 })) { isVisible = !isVisible; } ImGui::PopStyleColor(); } - - ImGui::PopStyleVar(); - + if (open) { // Caching list to prevent deletion while iterating. @@ -438,7 +435,8 @@ namespace Nuake { ImGui::TreePop(); } - + + ImGui::PopStyleVar(); ImGui::PopFont(); } @@ -763,6 +761,21 @@ namespace Nuake { ImGui::TableSetupColumn("set", 0, 0.6); ImGui::TableSetupColumn("reset", 0, 0.1); + ImGui::TableNextColumn(); + { + // Title + ImGui::Text("SSAO"); + ImGui::TableNextColumn(); + + ImGui::Checkbox("##SSAOEnabled", &env->SSAOEnabled); + ImGui::TableNextColumn(); + + // Reset button + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + std::string resetSSAO = ICON_FA_UNDO + std::string("##resetSSAO"); + if (ImGui::Button(resetSSAO.c_str())) env->SSAOEnabled = false; + ImGui::PopStyleColor(); + } ImGui::TableNextColumn(); { // Title @@ -854,10 +867,25 @@ namespace Nuake { ImGui::TableSetupColumn("set", 0, 0.6); ImGui::TableSetupColumn("reset", 0, 0.1); - ImGui::TableNextColumn(); - SSR* ssr = scene->m_SceneRenderer->mSSR.get(); { + ImGui::TableNextColumn(); + // Title + ImGui::Text("SSR"); + ImGui::TableNextColumn(); + + ImGui::Checkbox("##SSREnabled", &env->SSREnabled); + ImGui::TableNextColumn(); + + // Reset button + ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(1, 1, 1, 0)); + std::string resetSSR = ICON_FA_UNDO + std::string("##resetSSR"); + if (ImGui::Button(resetSSR.c_str())) env->SSREnabled = false; + ImGui::PopStyleColor(); + } + + { + ImGui::TableNextColumn(); // Title ImGui::Text("SSR RayStep"); ImGui::TableNextColumn(); @@ -1017,6 +1045,7 @@ namespace Nuake { std::string title = ICON_FA_TREE + std::string(" Hierarchy"); ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0)); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(4, 0)); if (ImGui::Begin(title.c_str())) { // Buttons to add and remove entity. @@ -1038,16 +1067,18 @@ namespace Nuake { ImGui::EndChild(); // Draw a tree of entities. ImGui::PushStyleColor(ImGuiCol_ChildBg, ImVec4(26.f / 255.0f, 26.f / 255.0f, 26.f / 255.0f, 1)); + if (ImGui::BeginChild("Scene tree", ImGui::GetContentRegionAvail(), false)) { - if (ImGui::BeginTable("entity_table", 2, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_NoBordersInBody | ImGuiTableFlags_SizingStretchProp)) + if (ImGui::BeginTable("entity_table", 3, ImGuiTableFlags_BordersInnerV | ImGuiTableFlags_SizingStretchProp)) { - ImGui::TableSetupColumn(" Label", ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentEnable); - std::string icon = ICON_FA_EYE; - ImGui::TableSetupColumn((" " + icon).c_str(), ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentDisable | ImGuiTableColumnFlags_WidthFixed, 32); + ImGui::TableSetupColumn("Label", ImGuiTableColumnFlags_IndentEnable); + ImGui::TableSetupColumn("Type", ImGuiTableColumnFlags_IndentEnable); + ImGui::TableSetupColumn("Visibility", ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_IndentDisable | ImGuiTableColumnFlags_WidthFixed); ImGui::TableHeadersRow(); ImGui::TableNextRow(); + ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2(0, 0)); std::vector entities = scene->GetAllEntities(); for (Entity e : entities) { @@ -1060,16 +1091,11 @@ namespace Nuake { // Write in normal font. ImGui::PushFont(normalFont); - // Small icons + name. - std::string label = ICON_FA_CIRCLE + std::string(" ") + name; - // Draw all entity without parents. if (!e.GetComponent().HasParent) { - ImGui::PushStyleVar(ImGuiStyleVar_CellPadding, ImVec2{ 0.0f, 0.0f }); // Recursively draw childrens. DrawEntityTree(e); - ImGui::PopStyleVar(); } // Pop font. @@ -1079,6 +1105,7 @@ namespace Nuake { //if (ImGui::BeginPopupContextItem()) // ImGui::EndPopup(); } + ImGui::PopStyleVar(); } ImGui::EndTable(); @@ -1110,9 +1137,9 @@ namespace Nuake { QueueDeletion = Entity{ (entt::entity)-1, scene.get() }; } } - ImGui::End(); ImGui::PopStyleVar(); + ImGui::PopStyleVar(); } bool EditorInterface::EntityContainsItself(Entity source, Entity target) @@ -1632,4 +1659,36 @@ namespace Nuake { ImGui::PushStyleVar(ImGuiStyleVar_ButtonTextAlign, ImVec2(0.5f, 0.5f)); FontManager::LoadFonts(); } + + std::string EditorInterface::GetEntityTypeName(const Entity& entity) const + { + std::string entityTypeName = ""; + + if (entity.HasComponent()) + { + entityTypeName = "Light"; + } + + if (entity.HasComponent()) + { + entityTypeName = "Rigidbody"; + } + + if (entity.HasComponent()) + { + entityTypeName = "Character Controller"; + } + + if (entity.HasComponent()) + { + entityTypeName = "Bone"; + } + + if (entity.HasComponent()) + { + entityTypeName = "Prefab"; + } + + return entityTypeName; + } } diff --git a/Editor/src/Windows/EditorInterface.h b/Editor/src/Windows/EditorInterface.h index f71a9f8c..46b1dacb 100644 --- a/Editor/src/Windows/EditorInterface.h +++ b/Editor/src/Windows/EditorInterface.h @@ -61,5 +61,8 @@ namespace Nuake bool ShouldDrawAxis() const { return m_DrawAxis; } bool ShouldDrawCollision() const { return m_DebugCollisions; } + + private: + std::string GetEntityTypeName(const Entity& entity) const; }; } diff --git a/Editor/src/Windows/EditorSelectionPanel.cpp b/Editor/src/Windows/EditorSelectionPanel.cpp index bb77a732..addbb26a 100644 --- a/Editor/src/Windows/EditorSelectionPanel.cpp +++ b/Editor/src/Windows/EditorSelectionPanel.cpp @@ -104,6 +104,7 @@ void EditorSelectionPanel::DrawEntity(Nuake::Entity entity) mSpritePanel.Draw(entity); mMeshPanel.Draw(entity); mSkinnedModelPanel.Draw(entity); + mBonePanel.Draw(entity); mQuakeMapPanel.Draw(entity); mCameraPanel.Draw(entity); mRigidbodyPanel.Draw(entity); @@ -131,8 +132,11 @@ void EditorSelectionPanel::DrawAddComponentMenu(Nuake::Entity entity) MenuItemComponent("Wren Script", Nuake::WrenScriptComponent) MenuItemComponent("Camera", Nuake::CameraComponent) MenuItemComponent("Light", Nuake::LightComponent) + ImGui::Separator(); MenuItemComponent("Model", Nuake::ModelComponent) MenuItemComponent("Skinned Model", Nuake::SkinnedModelComponent) + MenuItemComponent("Bone", Nuake::BoneComponent) + ImGui::Separator(); MenuItemComponent("Sprite", Nuake::SpriteComponent) MenuItemComponent("Particle Emitter", Nuake::ParticleEmitterComponent) ImGui::Separator(); diff --git a/Editor/src/Windows/EditorSelectionPanel.h b/Editor/src/Windows/EditorSelectionPanel.h index 03f71bc2..1e608200 100644 --- a/Editor/src/Windows/EditorSelectionPanel.h +++ b/Editor/src/Windows/EditorSelectionPanel.h @@ -21,7 +21,7 @@ #include "../ComponentsPanel/SpritePanel.h" #include "../ComponentsPanel/ParticleEmitterPanel.h" #include "../ComponentsPanel/SkinnedModelPanel.h" - +#include "../ComponentsPanel/BonePanel.h" class EditorSelectionPanel { @@ -42,6 +42,7 @@ private: SpritePanel mSpritePanel; CharacterControllerPanel mCharacterControllerPanel; ParticleEmitterPanel mParticleEmitterPanel; + BonePanel mBonePanel; Ref currentFile; Ref selectedResource; diff --git a/Editor/src/Windows/FileSystemUI.cpp b/Editor/src/Windows/FileSystemUI.cpp index a0b54fd6..68dd2361 100644 --- a/Editor/src/Windows/FileSystemUI.cpp +++ b/Editor/src/Windows/FileSystemUI.cpp @@ -62,6 +62,7 @@ namespace Nuake void FileSystemUI::DrawDirectory(Ref directory, uint32_t drawId) { ImGui::PushFont(FontManager::GetFont(Icons)); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); const char* icon = ICON_FA_FOLDER; const std::string id = ICON_FA_FOLDER + std::string("##") + directory->name; ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); @@ -69,9 +70,7 @@ namespace Nuake { m_CurrentDirectory = directory; } - ImGui::PopStyleVar(); - const std::string hoverMenuId = std::string("item_hover_menu") + std::to_string(drawId); if (ImGui::IsItemHovered() && ImGui::IsMouseReleased(1)) { @@ -191,6 +190,7 @@ namespace Nuake void FileSystemUI::DrawFile(Ref file, uint32_t drawId) { ImGui::PushFont(EditorInterface::bigIconFont); + ImGui::PushStyleVar(ImGuiStyleVar_FrameRounding, 4.0f); std::string fileExtension = file->GetExtension(); if (fileExtension == ".png" || fileExtension == ".jpg") { @@ -215,7 +215,7 @@ namespace Nuake Editor->Selection = EditorSelection(file); } } - + ImGui::PopStyleVar(); if (ImGui::BeginDragDropSource()) { char pathBuffer[256]; @@ -229,7 +229,7 @@ namespace Nuake { dragType = "_Map"; } - else if (fileExtension == ".obj" || fileExtension == ".mdl" || fileExtension == ".gltf" || fileExtension == ".md3" || fileExtension == ".fbx") + else if (fileExtension == ".obj" || fileExtension == ".mdl" || fileExtension == ".gltf" || fileExtension == ".md3" || fileExtension == ".fbx" || fileExtension == ".glb") { dragType = "_Model"; } @@ -676,8 +676,8 @@ namespace Nuake if (child) { int width = avail.x; - ImVec2 buttonSize = ImVec2(110, 110); - int amount = (int)(width / buttonSize.x); + ImVec2 buttonSize = ImVec2(80, 80); + int amount = (int)(width / 110); if (amount <= 0) amount = 1; int i = 1; // current amount of item per row. diff --git a/Nuake/dependencies/assimp b/Nuake/dependencies/assimp index 199aa5dd..9519a62d 160000 --- a/Nuake/dependencies/assimp +++ b/Nuake/dependencies/assimp @@ -1 +1 @@ -Subproject commit 199aa5dd663402d4d3461876c9846c45b616699d +Subproject commit 9519a62dd20799c5493c638d1ef5a6f484e5faf1 diff --git a/Nuake/dependencies/assimp_p5.lua b/Nuake/dependencies/assimp_p5.lua index 0d1a386e..c87cc532 100644 --- a/Nuake/dependencies/assimp_p5.lua +++ b/Nuake/dependencies/assimp_p5.lua @@ -96,7 +96,8 @@ project 'assimp' 'ASSIMP_BUILD_NO_TERRAGEN_IMPORTER', 'ASSIMP_BUILD_NO_X_IMPORTER', 'ASSIMP_BUILD_NO_X3D_IMPORTER', - 'ASSIMP_BUILD_NO_XGL_IMPORTER' + 'ASSIMP_BUILD_NO_XGL_IMPORTER', + 'ASSIMP_BUILD_NO_IQM_IMPORTER' } -- Exporters defines { diff --git a/Nuake/dependencies/build/assimp/config.h b/Nuake/dependencies/build/assimp/config.h index c7ba9c08..63e664ae 100644 --- a/Nuake/dependencies/build/assimp/config.h +++ b/Nuake/dependencies/build/assimp/config.h @@ -547,6 +547,15 @@ enum aiComponent // Various stuff to fine-tune the behaviour of specific importer plugins. // ########################################################################### +// --------------------------------------------------------------------------- +/** @brief Importers which parse JSON may use this to obtain a pointer to a + * rapidjson::IRemoteSchemaDocumentProvider. + * + * The default value is nullptr + * Property type: void* + */ +#define AI_CONFIG_IMPORT_SCHEMA_DOCUMENT_PROVIDER \ + "IMPORT_SCHEMA_DOCUMENT_PROVIDER" // --------------------------------------------------------------------------- /** @brief Set whether the fbx importer will merge all geometry layers present @@ -682,6 +691,15 @@ enum aiComponent #define AI_CONFIG_FBX_CONVERT_TO_M \ "AI_CONFIG_FBX_CONVERT_TO_M" + // --------------------------------------------------------------------------- + /** @brief Will enable the skeleton struct to store bone data. + * + * This will decouple the bone coupling to the mesh. This feature is + * experimental. + */ +#define AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER \ + "AI_CONFIG_FBX_USE_SKELETON_BONE_CONTAINER" + // --------------------------------------------------------------------------- /** @brief Set the vertex animation keyframe to be imported * diff --git a/Nuake/src/Core/Core.h b/Nuake/src/Core/Core.h index 181d234a..9b73304f 100644 --- a/Nuake/src/Core/Core.h +++ b/Nuake/src/Core/Core.h @@ -1,6 +1,7 @@ #pragma once #include #include +#include #include #include diff --git a/Nuake/src/Rendering/Mesh/Animation.cpp b/Nuake/src/Rendering/Mesh/Animation.cpp new file mode 100644 index 00000000..1bbab93e --- /dev/null +++ b/Nuake/src/Rendering/Mesh/Animation.cpp @@ -0,0 +1,10 @@ +#include + +namespace Nuake +{ + class Animation + { + Animation() = default; + + }; +} \ No newline at end of file diff --git a/Nuake/src/Rendering/Mesh/SkinnedMesh.cpp b/Nuake/src/Rendering/Mesh/SkinnedMesh.cpp index 49db585a..8d97a2d4 100644 --- a/Nuake/src/Rendering/Mesh/SkinnedMesh.cpp +++ b/Nuake/src/Rendering/Mesh/SkinnedMesh.cpp @@ -11,6 +11,7 @@ #include "src/Rendering/Buffers/VertexArray.h" #include "src/Rendering/Buffers/VertexBufferLayout.h" + #include namespace Nuake @@ -19,7 +20,6 @@ namespace Nuake { m_Vertices = vertices; m_Indices = indices; - m_Bones = bones; SetupMesh(); CalculateAABB(); @@ -40,11 +40,6 @@ namespace Nuake return m_Indices; } - std::vector& SkinnedMesh::GetBones() - { - return m_Bones; - } - Ref SkinnedMesh::GetMaterial() inline const { return m_Material; @@ -128,10 +123,6 @@ namespace Nuake j["Material"] = m_Material->Serialize(); j["Indices"] = m_Indices; - for (uint32_t i = 0; i < m_Bones.size(); i++) - { - //j["Bones"][i] = m_Bones[i]; - } json v; for (uint32_t i = 0; i < m_Vertices.size(); i++) { @@ -154,6 +145,12 @@ namespace Nuake v["Bitangent"]["y"] = m_Vertices[i].bitangent.y; v["Bitangent"]["z"] = m_Vertices[i].bitangent.z; + for (uint32_t b = 0; b < MAX_BONE_INFLUENCE; b++) + { + v["Weight"][b] = m_Vertices[i].weights[b]; + v["BoneIDs"][b] = m_Vertices[i].boneIDs[b]; + } + j["Vertices"][i] = v; } @@ -186,10 +183,17 @@ namespace Nuake vertex.uv = { 0.0, 0.0 }; } DESERIALIZE_VEC3(v["Position"], vertex.position) - DESERIALIZE_VEC3(v["Normal"], vertex.normal) - DESERIALIZE_VEC3(v["Tangent"], vertex.tangent) - DESERIALIZE_VEC3(v["Bitangent"], vertex.bitangent) - vertices.push_back(vertex); + DESERIALIZE_VEC3(v["Normal"], vertex.normal) + DESERIALIZE_VEC3(v["Tangent"], vertex.tangent) + DESERIALIZE_VEC3(v["Bitangent"], vertex.bitangent) + + for (uint32_t i = 0; i < MAX_BONE_INFLUENCE; i++) + { + vertex.weights[i] = v["Weight"][i]; + vertex.boneIDs[i] = v["boneIDs"][i]; + } + + vertices.push_back(vertex); } } ); diff --git a/Nuake/src/Rendering/Mesh/SkinnedMesh.h b/Nuake/src/Rendering/Mesh/SkinnedMesh.h index 7c0a2017..c8af79f7 100644 --- a/Nuake/src/Rendering/Mesh/SkinnedMesh.h +++ b/Nuake/src/Rendering/Mesh/SkinnedMesh.h @@ -3,6 +3,7 @@ #include "src/Rendering/AABB.h" #include "src/Resource/Resource.h" #include "src/Resource/Serializable.h" +#include "src/Resource/SkeletalAnimation.h" #include "src/Rendering/Vertex.h" #include "src/Rendering/Mesh/Bone.h" @@ -25,7 +26,6 @@ namespace Nuake void AddSurface(std::vector vertices, std::vector indices, std::vector bones); std::vector& GetVertices(); std::vector& GetIndices(); - std::vector& GetBones(); Ref GetMaterial() inline const; void SetMaterial(Ref material); @@ -43,7 +43,6 @@ namespace Nuake Ref m_Material = nullptr; std::vector m_Indices; std::vector m_Vertices; - std::vector m_Bones; Scope m_VertexBuffer; Scope m_VertexArray; diff --git a/Nuake/src/Rendering/PostFX/SSAO.cpp b/Nuake/src/Rendering/PostFX/SSAO.cpp index 757f0f5b..5b7f7c7b 100644 --- a/Nuake/src/Rendering/PostFX/SSAO.cpp +++ b/Nuake/src/Rendering/PostFX/SSAO.cpp @@ -40,6 +40,16 @@ namespace Nuake _ssaoBlurFramebuffer->QueueResize(size); } + void SSAO::Clear() + { + _ssaoBlurFramebuffer->Bind(); + { + RenderCommand::SetClearColor({ 1, 1, 1, 1}); + _ssaoBlurFramebuffer->Clear(); + } + _ssaoBlurFramebuffer->Unbind(); + } + void SSAO::GenerateKernel() { std::uniform_real_distribution randomFloats(0.0, 1.0); // random floats between [0.0, 1.0] diff --git a/Nuake/src/Rendering/PostFX/SSAO.h b/Nuake/src/Rendering/PostFX/SSAO.h index 5c37cc4a..d3737143 100644 --- a/Nuake/src/Rendering/PostFX/SSAO.h +++ b/Nuake/src/Rendering/PostFX/SSAO.h @@ -33,6 +33,7 @@ namespace Nuake float Falloff = 0.002f; float Strength = 0.2f; void Resize(const Vector2& size); + void Clear(); SSAO(); void Draw(FrameBuffer* gBuffer, const Matrix4& projection, const Matrix4& view); Ref GetOuput() const; diff --git a/Nuake/src/Rendering/Renderer.cpp b/Nuake/src/Rendering/Renderer.cpp index da239a74..c6490846 100644 --- a/Nuake/src/Rendering/Renderer.cpp +++ b/Nuake/src/Rendering/Renderer.cpp @@ -53,7 +53,7 @@ namespace Nuake { Vector3(-0.5f, 0.5f, 0.5f), Vector2(1, 1), Vector3(-1, 0, 0) } }; - std::vector CubeIndices + std::vector CubeIndices { 0, 1, 3, 3, 1, 2, 1, 5, 2, 2, 5, 6, diff --git a/Nuake/src/Rendering/SceneRenderer.cpp b/Nuake/src/Rendering/SceneRenderer.cpp index 87f61caa..8f74a7ca 100644 --- a/Nuake/src/Rendering/SceneRenderer.cpp +++ b/Nuake/src/Rendering/SceneRenderer.cpp @@ -56,8 +56,15 @@ namespace Nuake // SSAO const auto& sceneEnv = scene.GetEnvironment(); - sceneEnv->mSSAO->Resize(framebuffer.GetSize()); - sceneEnv->mSSAO->Draw(mGBuffer.get(), mProjection, mView); + if (sceneEnv->SSAOEnabled) + { + sceneEnv->mSSAO->Resize(framebuffer.GetSize()); + sceneEnv->mSSAO->Draw(mGBuffer.get(), mProjection, mView); + } + else + { + sceneEnv->mSSAO->Clear(); + } mShadingBuffer->QueueResize(framebuffer.GetSize()); ShadingPass(scene); @@ -69,7 +76,7 @@ namespace Nuake sceneEnv->mBloom->Resize(framebuffer.GetSize()); sceneEnv->mBloom->Draw(); - finalOutput = scene.GetEnvironment()->mBloom->GetOutput(); + finalOutput = sceneEnv->mBloom->GetOutput(); } const auto view = scene.m_Registry.view(); @@ -81,7 +88,7 @@ namespace Nuake lightList.push_back(lc); } - if (scene.GetEnvironment()->VolumetricEnabled) + if (sceneEnv->VolumetricEnabled) { sceneEnv->mVolumetric->Resize(framebuffer.GetSize()); sceneEnv->mVolumetric->SetDepth(mGBuffer->GetTexture(GL_DEPTH_ATTACHMENT).get()); @@ -117,8 +124,6 @@ namespace Nuake finalOutput = framebuffer.GetTexture(); - - // Copy final output to target framebuffer mToneMapBuffer->QueueResize(framebuffer.GetSize()); mToneMapBuffer->Bind(); @@ -134,20 +139,36 @@ namespace Nuake } mToneMapBuffer->Unbind(); - mSSR->Resize(framebuffer.GetSize()); - mSSR->Draw(mGBuffer.get(), framebuffer.GetTexture(), mView, mProjection, scene.GetCurrentCamera()); - - framebuffer.Bind(); + if (sceneEnv->SSREnabled) { - RenderCommand::Clear(); - Shader* shader = ShaderManager::GetShader("resources/Shaders/combine.shader"); - shader->Bind(); + mSSR->Resize(framebuffer.GetSize()); + mSSR->Draw(mGBuffer.get(), framebuffer.GetTexture(), mView, mProjection, scene.GetCurrentCamera()); - shader->SetUniformTex("u_Source", mToneMapBuffer->GetTexture().get(), 0); - shader->SetUniformTex("u_Source2", mSSR->OutputFramebuffer->GetTexture().get(), 1); - Renderer::DrawQuad(); + framebuffer.Bind(); + { + RenderCommand::Clear(); + Shader* shader = ShaderManager::GetShader("resources/Shaders/combine.shader"); + shader->Bind(); + + shader->SetUniformTex("u_Source", mToneMapBuffer->GetTexture().get(), 0); + shader->SetUniformTex("u_Source2", mSSR->OutputFramebuffer->GetTexture().get(), 1); + Renderer::DrawQuad(); + } + framebuffer.Unbind(); + } + else + { + framebuffer.Bind(); + { + RenderCommand::Clear(); + Shader* shader = ShaderManager::GetShader("resources/Shaders/copy.shader"); + shader->Bind(); + + shader->SetUniformTex("u_Source", mToneMapBuffer->GetTexture().get(), 0); + Renderer::DrawQuad(); + } + framebuffer.Unbind(); } - framebuffer.Unbind(); RenderCommand::Enable(RendererEnum::DEPTH_TEST); Renderer::EndDraw(); @@ -170,7 +191,9 @@ namespace Nuake { auto [lightTransform, light, visibility] = view.get(l); if (light.Type != LightType::Directional || !light.CastShadows || !visibility.Visible) + { continue; + } light.CalculateViewProjection(mView, mProjection); @@ -359,30 +382,28 @@ namespace Nuake gBufferSkinnedMeshShader->SetUniformMat4f("u_Projection", mProjection); gBufferSkinnedMeshShader->SetUniformMat4f("u_View", mView); + RenderCommand::Disable(RendererEnum::FACE_CULL); + // Skinned Models const uint32_t entityIdUniformLocation = gBufferSkinnedMeshShader->FindUniformLocation("u_EntityID"); const uint32_t modelMatrixUniformLocation = gBufferSkinnedMeshShader->FindUniformLocation("u_Model"); - + gBufferSkinnedMeshShader->SetUniformMat4f(modelMatrixUniformLocation, Matrix4(1.0f)); auto skinnedModelView = scene.m_Registry.view(); for (auto e : skinnedModelView) { auto [transform, mesh, visibility] = skinnedModelView.get(e); + auto& meshResource = mesh.ModelResource; - if (mesh.ModelResource && visibility.Visible) + if (meshResource && visibility.Visible) { + auto& rootBoneNode = meshResource->GetSkeletonRootNode(); + + SetSkeletonBoneTransformRecursive(rootBoneNode, gBufferSkinnedMeshShader); + for (auto& m : mesh.ModelResource->GetMeshes()) { m->GetMaterial()->Bind(gBufferSkinnedMeshShader); - uint32_t boneId = 0; - for (auto& b : m->GetBones()) - { - const std::string boneMatrixUniformName = "u_FinalBonesMatrice[" + std::to_string(boneId) + "]"; - gBufferSkinnedMeshShader->SetUniformMat4f(boneMatrixUniformName, b.Offset); - boneId++; - } - - gBufferSkinnedMeshShader->SetUniformMat4f(modelMatrixUniformLocation, transform.GetGlobalTransform()); gBufferSkinnedMeshShader->SetUniform1i(entityIdUniformLocation, (uint32_t)e + 1); m->Draw(gBufferSkinnedMeshShader, true); } @@ -452,4 +473,20 @@ namespace Nuake void SceneRenderer::PostProcessPass(const Scene& scene) { } + + void SceneRenderer::SetSkeletonBoneTransformRecursive(SkeletonNode& skeletonNode, Shader* shader) + { + auto scene = Engine::GetCurrentScene(); + for (auto& child : skeletonNode.Children) + { + if (auto entity = scene->GetEntity(child.Name); entity.GetHandle() != -1) + { + const std::string boneMatrixUniformName = "u_FinalBonesMatrice[" + std::to_string(child.Id) + "]"; + shader->SetUniformMat4f(boneMatrixUniformName, child.FinalTransform); + } + + SetSkeletonBoneTransformRecursive(child, shader); + } + } + } \ No newline at end of file diff --git a/Nuake/src/Rendering/SceneRenderer.h b/Nuake/src/Rendering/SceneRenderer.h index 1e4d9d72..267359ad 100644 --- a/Nuake/src/Rendering/SceneRenderer.h +++ b/Nuake/src/Rendering/SceneRenderer.h @@ -39,5 +39,7 @@ namespace Nuake void GBufferPass(Scene& scene); void ShadingPass(Scene& scene); void PostProcessPass(const Scene& scene); + + void SetSkeletonBoneTransformRecursive(SkeletonNode& skeletonNode, Shader* shader); }; } \ No newline at end of file diff --git a/Nuake/src/Rendering/Shaders/Shader.cpp b/Nuake/src/Rendering/Shaders/Shader.cpp index 17a7fa2d..63e392cf 100644 --- a/Nuake/src/Rendering/Shaders/Shader.cpp +++ b/Nuake/src/Rendering/Shaders/Shader.cpp @@ -295,7 +295,7 @@ namespace Nuake if (addr != -1) { - SetUniformMat4f(addr, std::move(mat)); + SetUniformMat4f(addr, mat); } } diff --git a/Nuake/src/Resource/Animator.cpp b/Nuake/src/Resource/Animator.cpp new file mode 100644 index 00000000..c369bf19 --- /dev/null +++ b/Nuake/src/Resource/Animator.cpp @@ -0,0 +1,29 @@ +#include "Animator.h" + +#include "src/Core/Logger.h" + +namespace Nuake +{ + Animator::Animator(std::vector& animations) + { + m_AnimationCount = animations.size(); + m_CurrentAnimation = 0; + + m_Animations = animations; + m_CurrentTime = 0.0f; + m_DeltaTime = 0.0f; + } + + void Animator::PlayAnimation(uint32_t index) + { + if (index < m_AnimationCount) + { + m_CurrentAnimation = index; + } + else + { + m_CurrentAnimation = m_AnimationCount - 1; + Logger::Log("Animation index out of range", "animator", WARNING); + } + } +} \ No newline at end of file diff --git a/Nuake/src/Resource/Animator.h b/Nuake/src/Resource/Animator.h new file mode 100644 index 00000000..72a56a37 --- /dev/null +++ b/Nuake/src/Resource/Animator.h @@ -0,0 +1,28 @@ +#pragma once +#include "src/Core/Core.h" +#include "SkeletalAnimation.h" + +namespace Nuake +{ + class Animator + { + private: + float m_CurrentTime; + float m_DeltaTime; + + uint32_t m_CurrentAnimation; + uint32_t m_AnimationCount; + std::vector m_Animations; + + public: + Animator(std::vector& animations); + ~Animator() = default; + + void Update(float deltaTime); + void PlayAnimation(uint32_t index); + + void CalculateBoneTransform(Matrix4& parentTransform); + + std::vector& GetFinalBoneMatrices(); + }; +} \ No newline at end of file diff --git a/Nuake/src/Resource/ModelLoader.cpp b/Nuake/src/Resource/ModelLoader.cpp index d7b26160..d9ab3048 100644 --- a/Nuake/src/Resource/ModelLoader.cpp +++ b/Nuake/src/Resource/ModelLoader.cpp @@ -7,7 +7,7 @@ #include "src/Resource/SkinnedModel.h" #include "src/Resource/Model.h" - +#include "src/Resource/SkeletalAnimation.h" namespace Nuake { @@ -50,8 +50,13 @@ namespace Nuake return model; } + + std::unordered_map bonesNameIDMap; Ref ModelLoader::LoadSkinnedModel(const std::string& path, bool absolute) { + bonesNameIDMap = std::unordered_map(); + m_BoneMap = std::unordered_map(); + m_SkinnedMeshes.clear(); Ref model = CreateRef(path); @@ -62,7 +67,8 @@ namespace Nuake aiProcess_Triangulate | aiProcess_GenSmoothNormals | aiProcess_FixInfacingNormals | - aiProcess_CalcTangentSpace; + aiProcess_CalcTangentSpace | + aiProcess_OptimizeGraph; modelDir = absolute ? path + "/../" : FileSystem::Root + path + "/../"; const std::string filePath = absolute ? path : FileSystem::Root + path; @@ -70,13 +76,80 @@ namespace Nuake if (!scene || scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE || !scene->mRootNode) { std::string assimpErrorMsg = std::string(importer.GetErrorString()); - std::string logMsg = "[Failed to load model] - " + assimpErrorMsg; + std::string logMsg = "Failed to load model: " + assimpErrorMsg; Logger::Log(logMsg, "model", WARNING); return model; } ProcessSkinnedNode(scene->mRootNode, scene); + if (scene->HasAnimations()) + { + std::vector> animations = std::vector>(); + + // Parse animations + for (uint32_t i = 0; i < scene->mNumAnimations; i++) + { + aiAnimation* aiAnim = scene->mAnimations[i]; + + const std::string animationName = aiAnim->mName.data; + const float animationDuration = aiAnim->mDuration; + const float animationTicksPerSecond = aiAnim->mTicksPerSecond; + auto animation = CreateRef(animationName, animationDuration, animationTicksPerSecond); + + // Here we iterate over every channel(each channel represents a bone) and fill the SkeletalAnimation + // object with every keyframe. We essentially just convert every assimp vector, quat, string to our own types. + for (uint32_t j = 0; j < aiAnim->mNumChannels; j++) + { + aiNodeAnim* animChannel = aiAnim->mChannels[j]; + const std::string channelName = animChannel->mNodeName.data; // this should be a bone name + + // Create the track + BoneTransformTrack& track = animation->GetTrack(channelName); + + // Create every keyframe in the current channel + // Position + for (uint32_t p = 0; p < animChannel->mNumPositionKeys; p++) + { + aiVectorKey positionKey = animChannel->mPositionKeys[p]; + const float keyTime = positionKey.mTime; + const aiVector3D assimpKeyValue = positionKey.mValue; + const Vector3 keyValue = Vector3(assimpKeyValue.x, assimpKeyValue.y, assimpKeyValue.z); + + track.PushPositionKeyframe(keyTime, keyValue); + } + + // Rotation + for (uint32_t r = 0; r < animChannel->mNumRotationKeys; r++) + { + aiQuatKey rotationKey = animChannel->mRotationKeys[r]; + const float keyTime = rotationKey.mTime; + const aiQuaterniont assimpKeyValue = rotationKey.mValue; + const Quat keyValue = Quat(assimpKeyValue.w, assimpKeyValue.x, assimpKeyValue.y, assimpKeyValue.z); + + track.PushRotationKeyframe(keyTime, keyValue); + } + + // Scaling + for (uint32_t s = 0; s < animChannel->mNumScalingKeys; s++) + { + aiVectorKey scaleKey = animChannel->mScalingKeys[s]; + const float keyTime = scaleKey.mTime; + const aiVector3D assimpKeyValue = scaleKey.mValue; + const Vector3 keyValue = Vector3(assimpKeyValue.x, assimpKeyValue.y, assimpKeyValue.z); + + track.PushScaleKeyframe(keyTime, keyValue); + } + } + + animations.push_back(animation); + } + + SkeletonNode rootSkeletonNode; + ProcessAnimationNode(rootSkeletonNode, scene->mRootNode); + model->SetSkeletonRootNode(std::move(rootSkeletonNode)); + model->SetAnimations(std::move(animations)); + } for (const auto& mesh : m_SkinnedMeshes) { @@ -120,7 +193,6 @@ namespace Nuake auto& indices = ProcessIndices(node); auto& material = ProcessMaterials(scene, node); auto& bones = std::vector(); - auto& bonesMap = std::unordered_map(); if (node->HasBones()) { @@ -131,7 +203,7 @@ namespace Nuake const std::string& boneName = bone->mName.C_Str(); int32_t boneId = -1; - if (bonesMap.find(boneName) == bonesMap.end()) + if (m_BoneMap.find(boneName) == m_BoneMap.end()) { boneId = boneCounter; boneCounter++; @@ -140,11 +212,12 @@ namespace Nuake newBone.Offset = ConvertMatrixToGLMFormat(bone->mOffsetMatrix); bones.push_back(newBone); - bonesMap[boneName] = newBone; + bonesNameIDMap[boneName] = boneId; + m_BoneMap[boneName] = newBone; } else { - boneId = bonesMap[boneName].Id; + boneId = m_BoneMap[boneName].Id; } assert(boneId != -1); @@ -154,7 +227,7 @@ namespace Nuake { aiVertexWeight vertexWeight = bone->mWeights[j]; const uint32_t vertexWeightVertexId = vertexWeight.mVertexId; - + SetVertexBoneData(vertices[vertexWeightVertexId], boneId, vertexWeight.mWeight); //BoneVertexWeight boneVertexWeight @@ -177,7 +250,6 @@ namespace Nuake Ref mesh = CreateRef(); mesh->AddSurface(vertices, indices, bones); mesh->SetMaterial(material); - return mesh; } @@ -199,20 +271,20 @@ namespace Nuake auto vertices = std::vector(); for (uint32_t i = 0; i < mesh->mNumVertices; i++) { - Vertex vertex; + Vertex vertex {}; Vector3 current; // Position current.x = mesh->mVertices[i].x; - current.y = mesh->mVertices[i].z; - current.z = mesh->mVertices[i].y; + current.y = mesh->mVertices[i].y; + current.z = mesh->mVertices[i].z; vertex.position = current; // Normals current.x = mesh->mNormals[i].x; - current.y = mesh->mNormals[i].z; - current.z = mesh->mNormals[i].y; + current.y = mesh->mNormals[i].y; + current.z = mesh->mNormals[i].z; vertex.normal = current; // Tangents @@ -309,6 +381,18 @@ namespace Nuake void ModelLoader::SetVertexBoneData(SkinnedVertex& vertex, int boneID, float weight) { + for (int i = 0; i < MAX_BONE_INFLUENCE; ++i) + { + if (vertex.boneIDs[i] == boneID) { + return; + } + } + + if (weight == 0.0f) + { + return; + } + for (int i = 0; i < MAX_BONE_INFLUENCE; ++i) { if (vertex.boneIDs[i] < 0) @@ -320,6 +404,42 @@ namespace Nuake } } + void ModelLoader::ProcessAnimationNode(SkeletonNode& dest, const aiNode* src) + { + assert(src); + + dest.Name = src->mName.data; + dest.Transform = ConvertMatrixToGLMFormat(src->mTransformation); + dest.ChildrenCount = src->mNumChildren; + dest.Id = bonesNameIDMap[dest.Name]; + dest.Offset = m_BoneMap[dest.Name].Offset; + + for (uint32_t i = 0; i < dest.ChildrenCount; i++) + { + SkeletonNode newNode; + ProcessAnimationNode(newNode, src->mChildren[i]); + dest.Children.push_back(std::move(newNode)); + } + } + + void ModelLoader::ProcessSkeleton(SkeletonNode& des, const aiNode* src) + { + // Create a Bone object for this node + SkeletonNode bone; + bone.Name = src->mName.C_Str(); + bone.Transform = ConvertMatrixToGLMFormat(src->mTransformation); + bone.Id = bonesNameIDMap[bone.Name]; + + des.ChildrenCount++; + des.Children.push_back(bone); + + // Recursively process child nodes (bones) + for (uint32_t i = 0; i < src->mNumChildren; i++) + { + ProcessSkeleton(bone, src->mChildren[i]); + } + } + std::vector ModelLoader::ProcessIndices(aiMesh* mesh) { auto indices = std::vector(); @@ -341,8 +461,11 @@ namespace Nuake return nullptr; aiMaterial* materialNode = scene->mMaterials[mesh->mMaterialIndex]; - Ref material = CreateRef(); + + aiString materialName; + materialNode->Get(AI_MATKEY_NAME, materialName); + material->SetName(std::string(materialName.C_Str())); aiString str; if (materialNode->GetTextureCount(aiTextureType_DIFFUSE) > 0) diff --git a/Nuake/src/Resource/ModelLoader.h b/Nuake/src/Resource/ModelLoader.h index 32340eb6..598be588 100644 --- a/Nuake/src/Resource/ModelLoader.h +++ b/Nuake/src/Resource/ModelLoader.h @@ -7,6 +7,7 @@ #include "src/Rendering/Textures/Texture.h" #include "src/Rendering/Vertex.h" +#include "src/Resource/SkeletonNode.h" #include "assimp/Importer.hpp" #include @@ -32,6 +33,8 @@ namespace Nuake std::string modelDir; std::vector> m_Meshes; std::vector> m_SkinnedMeshes; + std::unordered_map m_BoneIDMap; + std::unordered_map m_BoneMap; void ProcessNode(aiNode* node, const aiScene* scene); Ref ProcessMesh(aiMesh* node, const aiScene* scene); @@ -46,30 +49,19 @@ namespace Nuake Ref ProcessSkinnedMesh(aiMesh* node, const aiScene* scene); std::vector ProcessSkinnedVertices(aiMesh* mesh); void SetVertexBoneData(SkinnedVertex& vertex, int boneId, float weight); + void ProcessAnimationNode(SkeletonNode& dest, const aiNode* src); + void ProcessSkeleton(SkeletonNode& des, const aiNode* src); static inline Matrix4 ConvertMatrixToGLMFormat(const aiMatrix4x4& from) { - Matrix4 result; - for (auto i = 0; i < 3; i++) - { - for (auto j = 0; j < 3; j++) - { - result[i][j] = from[i][j]; - } - } + Matrix4 to; - // The rest would be zero, other than the 4,4. - result[0][3] = 0.0f; - result[1][3] = 0.0f; - result[2][3] = 0.0f; + to[0][0] = from.a1; to[0][1] = from.b1; to[0][2] = from.c1; to[0][3] = from.d1; + to[1][0] = from.a2; to[1][1] = from.b2; to[1][2] = from.c2; to[1][3] = from.d2; + to[2][0] = from.a3; to[2][1] = from.b3; to[2][2] = from.c3; to[2][3] = from.d3; + to[3][0] = from.a4; to[3][1] = from.b4; to[3][2] = from.c4; to[3][3] = from.d4; - result[3][0] = 0.0f; - result[3][1] = 0.0f; - result[3][2] = 0.0f; - - result[3][3] = 1.0f; - - return result; + return to; } }; } \ No newline at end of file diff --git a/Nuake/src/Resource/SkeletalAnimation.cpp b/Nuake/src/Resource/SkeletalAnimation.cpp new file mode 100644 index 00000000..80e5d180 --- /dev/null +++ b/Nuake/src/Resource/SkeletalAnimation.cpp @@ -0,0 +1,180 @@ +#include "SkeletalAnimation.h" + +namespace Nuake +{ + BoneTransformTrack::BoneTransformTrack() + { + m_Positions = std::vector(); + m_Rotations = std::vector(); + m_Scales = std::vector(); + + m_PositionTimestamps = std::vector(); + m_RotationTimestamps = std::vector(); + m_ScaleTimestamps = std::vector(); + } + + float BoneTransformTrack::GetScaleFactor(float lastTime, float nextTime, float animationTime) + { + float scaleFactor = 0.0f; + float midWayLength = animationTime - lastTime; + float framesDiff = nextTime - lastTime; + scaleFactor = midWayLength / framesDiff; + return scaleFactor; + } + + Nuake::Matrix4 BoneTransformTrack::InterpolatePosition(float time) + { + if (m_Positions.size() == 0) + { + return Matrix4(1.0f); + } + + if (m_Positions.size() == 1) + { + return glm::translate(Matrix4(1.0f), m_Positions[0]); + } + + int p0Index = GetPositionIndex(time); + int p1Index = p0Index + 1; + float scaleFactor = GetScaleFactor(m_PositionTimestamps[p0Index], m_PositionTimestamps[p1Index], time); + glm::vec3 finalPosition = glm::mix(m_Positions[p0Index], m_Positions[p1Index], scaleFactor); + return glm::translate(Matrix4(1.0f), finalPosition); + } + + Nuake::Matrix4 BoneTransformTrack::InterpolateRotation(float time) + { + if (m_Rotations.size() == 0) + { + return Matrix4(1.0f); + } + + if (m_Rotations.size() == 1) + { + auto rotation = glm::normalize(m_Rotations[0]); + return glm::toMat4(rotation); + } + + int p0Index = GetRotationIndex(time); + int p1Index = p0Index + 1; + float scaleFactor = GetScaleFactor(m_RotationTimestamps[p0Index], + m_RotationTimestamps[p1Index], time); + glm::quat finalRotation = glm::slerp(m_Rotations[p0Index], + m_Rotations[p1Index], scaleFactor); + finalRotation = glm::normalize(finalRotation); + return glm::toMat4(finalRotation); + } + + Nuake::Matrix4 BoneTransformTrack::InterpolateScale(float time) + { + if (m_Scales.size() == 0) + { + return Matrix4(1.0f); + } + + if (1 == m_Scales.size()) + return glm::scale(glm::mat4(1.0f), m_Scales[0]); + + int p0Index = GetScaleIndex(time); + int p1Index = p0Index + 1; + float scaleFactor = GetScaleFactor(m_ScaleTimestamps[p0Index], + m_ScaleTimestamps[p1Index], time); + glm::vec3 finalScale = glm::mix(m_Scales[p0Index], m_Scales[p1Index], scaleFactor); + return glm::scale(glm::mat4(1.0f), finalScale); + } + + json BoneTransformTrack::Serialize() + { + BEGIN_SERIALIZE(); + for (uint32_t i = 0; i < m_PositionTimestamps.size(); i++) + { + j["m_PositionTimestamps"][i] = m_PositionTimestamps[i]; + } + + for (uint32_t i = 0; i < m_Positions.size(); i++) + { + j["m_Positions"][i] = SERIALIZE_VEC3(m_Positions[i]); + } + + for (uint32_t i = 0; i < m_RotationTimestamps.size(); i++) + { + j["m_RotationTimestamps"][i] = m_RotationTimestamps[i]; + } + + for (uint32_t i = 0; i < m_Rotations.size(); i++) + { + j["m_Rotations"][i] = SERIALIZE_VEC4(m_Rotations[i]); + } + + for (uint32_t i = 0; i < m_ScaleTimestamps.size(); i++) + { + j["m_ScaleTimestamps"][i] = m_ScaleTimestamps[i]; + } + + for (uint32_t i = 0; i < m_Scales.size(); i++) + { + j["m_Scales"][i] = SERIALIZE_VEC3(m_Scales[i]); + } + + END_SERIALIZE(); + } + + bool BoneTransformTrack::Deserialize(const json& j) + { + return true; + } + + SkeletalAnimation::SkeletalAnimation(const std::string& name, float duration, float ticksPerSecond) + { + m_Name = name; + m_Duration = duration; + m_TicksPerSecond = ticksPerSecond; + m_CurrentTime = 0.0f; + m_Loop = true; + } + + BoneTransformTrack& SkeletalAnimation::GetTrack(const std::string& name) + { + if (m_Tracks.find(name) != m_Tracks.end()) + { + return m_Tracks[name]; + } + + m_Tracks[name] = BoneTransformTrack(); + return m_Tracks[name]; + } + + json SkeletalAnimation::Serialize() + { + BEGIN_SERIALIZE(); + SERIALIZE_VAL(m_Name); + SERIALIZE_VAL(m_Duration); + SERIALIZE_VAL(m_TicksPerSecond); + SERIALIZE_VAL(m_CurrentTime); + SERIALIZE_VAL(m_Loop); + + for (auto& t : m_Tracks) + { + j["Tracks"][t.first] = t.second.Serialize(); + } + + return json(); + } + + bool SkeletalAnimation::Deserialize(const json& j) + { + m_Name = j["m_Name"]; + m_Duration = j["m_Duration"]; + m_TicksPerSecond = j["m_TicksPerSecond"]; + m_CurrentTime = j["m_CurrentTime"]; + m_Loop = j["m_Loop"]; + + for (auto& [trackName, trackData]: j["Tracks"].items()) + { + BoneTransformTrack track; + track.Deserialize(j["Tracks"][trackName]); + m_Tracks[trackName] = std::move(track); + } + return true; + } + +} \ No newline at end of file diff --git a/Nuake/src/Resource/SkeletalAnimation.h b/Nuake/src/Resource/SkeletalAnimation.h index af3109f8..15824a9f 100644 --- a/Nuake/src/Resource/SkeletalAnimation.h +++ b/Nuake/src/Resource/SkeletalAnimation.h @@ -1,21 +1,178 @@ #pragma once +#include "src/Core/Core.h" #include +#include "src/Resource/Serializable.h" +#include "src/Resource/Resource.h" + namespace Nuake { - class Animation + struct SkeletonAnimationBoneAnimation + { + std::string Name; + Vector3 Positions; + Quat Rotations; + Vector3 Scalings; + }; + + class BoneTransformTrack { private: - float m_Duration; - int m_TicksPerSecond; - std::vector m_Bones; - //std::map m_BoneInfoMap; + std::vector m_PositionTimestamps = {}; + std::vector m_RotationTimestamps = {}; + std::vector m_ScaleTimestamps = {}; + + std::vector m_Positions = {}; + std::vector m_Rotations = {}; + std::vector m_Scales = {}; + + Matrix4 m_PositionTransform; + Matrix4 m_RotationTransform; + Matrix4 m_ScaleTransform; + Matrix4 m_FinalTransform; + + bool m_IsEmpty = true; public: - Animation() = default; - Animation(); + BoneTransformTrack(); + ~BoneTransformTrack() = default; - Bone& FindBone(const std::string& boneName); + bool IsEmpty() const { return m_IsEmpty; } + void PushPositionKeyframe(float timestamp, const Vector3& position) + { + m_IsEmpty = false; + m_PositionTimestamps.push_back(timestamp); + m_Positions.push_back(position); + } + + void PushRotationKeyframe(float timestamp, const Quat& rotation) + { + m_IsEmpty = false; + m_RotationTimestamps.push_back(timestamp); + m_Rotations.push_back(rotation); + } + + void PushScaleKeyframe(float timestamp, const Vector3& scale) + { + m_IsEmpty = false; + m_ScaleTimestamps.push_back(timestamp); + m_Scales.push_back(scale); + } + + int GetPositionIndex(float animationTime) + { + if (m_Positions.size() == 0) + { + return 0; + } + + for (int index = 0; index < m_Positions.size() - 1; index++) + { + if (animationTime < m_PositionTimestamps[index + 1]) + { + return index; + } + } + assert(0); + } + + void Update(float time) + { + const Matrix4 previousPos = m_PositionTransform; + const Matrix4 previousRot = m_RotationTransform; + const Matrix4 previousSca = m_ScaleTransform; + m_PositionTransform = InterpolatePosition(time); + m_RotationTransform = InterpolateRotation(time); + m_ScaleTransform = InterpolateScale(time); + + bool hasChanged = previousPos != m_PositionTransform || previousRot != m_RotationTransform || previousSca != m_ScaleTransform; + if (hasChanged) + { + m_FinalTransform = m_PositionTransform * m_RotationTransform * m_ScaleTransform; + } + } + + const Matrix4& GetFinalTransform() const + { + return m_FinalTransform; + } + + /* Gets the current index on mKeyRotations to interpolate to based on the + current animation time*/ + int GetRotationIndex(float animationTime) + { + for (int index = 0; index < m_Rotations.size() - 1; ++index) + { + if (animationTime < m_RotationTimestamps[index + 1]) + { + return index; + } + } + assert(0); + } + + /* Gets the current index on mKeyScalings to interpolate to based on the + current animation time */ + int GetScaleIndex(float animationTime) + { + for (int index = 0; index < m_Scales.size() - 1; ++index) + { + if (animationTime < m_ScaleTimestamps[index + 1]) + { + return index; + } + } + assert(0); + } + + float GetScaleFactor(float lastTime, float nextTime, float animationTime); + Matrix4 InterpolatePosition(float time); + Matrix4 InterpolateRotation(float time); + Matrix4 InterpolateScale(float time); + + json Serialize(); + bool Deserialize(const json& j); + }; + + class SkeletalAnimation : public Resource, ISerializable + { + private: + std::unordered_map m_Tracks; + float m_CurrentTime; + float m_Duration; + float m_TicksPerSecond; + std::string m_Name; + bool m_Loop = false; + + public: + SkeletalAnimation() = default; + SkeletalAnimation(const std::string& name, float duration, float ticksPerSecond); + + ~SkeletalAnimation() = default; + + void SetCurrentTime(float time) + { + if (m_Loop) + { + m_CurrentTime = fmod(time, m_Duration); + } + else + { + m_CurrentTime = time; + } + } + + float GetCurrentTime() const { return m_CurrentTime; } + void SetDuration(float duration) { m_Duration = duration; } + float GetDuration() const { return m_Duration; } float GetTicksPerSecond() const { return m_TicksPerSecond; } + void SetTicksPerSecond(float ticks) { m_TicksPerSecond = ticks; } + + const std::string& GetName() const { return m_Name; } + BoneTransformTrack& GetTrack(const std::string& name); + std::unordered_map& GetTracks() { return m_Tracks; } + + json Serialize() override; + bool Deserialize(const json& j) override; }; } diff --git a/Nuake/src/Resource/SkeletonNode.h b/Nuake/src/Resource/SkeletonNode.h new file mode 100644 index 00000000..32b54a3b --- /dev/null +++ b/Nuake/src/Resource/SkeletonNode.h @@ -0,0 +1,18 @@ +#pragma once +#include "src/Core/Core.h" +#include "src/Core/Maths.h" + +namespace Nuake +{ + struct SkeletonNode + { + Matrix4 Transform; + Matrix4 Offset; + std::string Name; + int ChildrenCount; + std::vector Children; + Matrix4 FinalTransform = Matrix4(1.0f); + int32_t Id = -1; + int32_t EntityHandle = 0; + }; +} \ No newline at end of file diff --git a/Nuake/src/Resource/SkinnedModel.cpp b/Nuake/src/Resource/SkinnedModel.cpp index a638fa91..ac71f490 100644 --- a/Nuake/src/Resource/SkinnedModel.cpp +++ b/Nuake/src/Resource/SkinnedModel.cpp @@ -27,6 +27,43 @@ namespace Nuake return m_Meshes; } + void SkinnedModel::SetAnimations(const std::vector> animations) + { + m_NumAnimation = static_cast(animations.size()); + m_CurrentAnimation = 0; + m_Animations = animations; + } + + void SkinnedModel::AddAnimation(Ref animation) + { + m_NumAnimation++; + m_Animations.push_back(std::move(animation)); + } + + Ref SkinnedModel::GetCurrentAnimation() + { + if (m_CurrentAnimation < m_NumAnimation) + { + return m_Animations[m_CurrentAnimation]; + } + + Logger::Log("Cannot get animation if no animation exists", "skinned model", WARNING); + return nullptr; + } + + void SkinnedModel::PlayAnimation(uint32_t animationId) + { + if (animationId >= m_NumAnimation) + { + Logger::Log("Cannot play animation, index out of range", "skinned model", CRITICAL); + return; + } + + GetCurrentAnimation()->SetCurrentTime(0.0f); // Reset previous animation + + m_CurrentAnimation = animationId; + } + json SkinnedModel::Serialize() { BEGIN_SERIALIZE(); @@ -41,7 +78,19 @@ namespace Nuake { j["Meshes"][i] = m_Meshes[i]->Serialize(); } + + j["m_CurrentAnimation"] = m_CurrentAnimation; + j["m_NumAnimation"] = m_NumAnimation; + + uint32_t a = 0; + for (auto& animation : m_Animations) + { + j["m_Animations"][a] = animation->Serialize(); + } + } + + END_SERIALIZE(); } @@ -54,6 +103,10 @@ namespace Nuake ModelLoader loader; auto otherModel = loader.LoadSkinnedModel(j["Path"], false); m_Meshes = otherModel->GetMeshes(); + m_Animations = otherModel->GetAnimations(); + m_SkeletonRoot = otherModel->GetSkeletonRootNode(); + m_NumAnimation = m_Animations.size(); + m_CurrentAnimation = 0; this->Path = j["Path"]; } diff --git a/Nuake/src/Resource/SkinnedModel.h b/Nuake/src/Resource/SkinnedModel.h index 96278d72..70e30a52 100644 --- a/Nuake/src/Resource/SkinnedModel.h +++ b/Nuake/src/Resource/SkinnedModel.h @@ -3,6 +3,7 @@ #include "src/Rendering/Mesh/SkinnedMesh.h" #include "src/Resource/Resource.h" #include "src/Resource/Serializable.h" +#include "src/Resource/SkeletonNode.h" namespace Nuake @@ -12,14 +13,37 @@ namespace Nuake private: std::vector> m_Meshes; + SkeletonNode m_SkeletonRoot; + + uint32_t m_CurrentAnimation = 0; + uint32_t m_NumAnimation = 0; + std::vector> m_Animations; + public: + bool IsPlaying = true; + SkinnedModel(); SkinnedModel(const std::string path); ~SkinnedModel(); + SkeletonNode& GetSkeletonRootNode() { return m_SkeletonRoot; } + void SetSkeletonRootNode(SkeletonNode& root) + { + m_SkeletonRoot = std::move(root); + } + void AddMesh(Ref mesh); std::vector>& GetMeshes(); + std::vector> GetAnimations() const { return m_Animations; } + void SetAnimations(const std::vector> animations); + void AddAnimation(Ref animation); + + Ref GetCurrentAnimation(); + void PlayAnimation(uint32_t animationId); + uint32_t GetCurrentAnimationIndex() const { return m_CurrentAnimation; } + uint32_t GetAnimationsCount() const { return m_NumAnimation; } + json Serialize() override; bool Deserialize(const json& j) override; }; diff --git a/Nuake/src/Scene/Components/BoneComponent.cpp b/Nuake/src/Scene/Components/BoneComponent.cpp new file mode 100644 index 00000000..d97d1a95 --- /dev/null +++ b/Nuake/src/Scene/Components/BoneComponent.cpp @@ -0,0 +1,8 @@ +#include "src/Core/Core.h" + +#include "BoneComponent.h" + +namespace Nuake +{ + +} \ No newline at end of file diff --git a/Nuake/src/Scene/Components/BoneComponent.h b/Nuake/src/Scene/Components/BoneComponent.h new file mode 100644 index 00000000..824484b9 --- /dev/null +++ b/Nuake/src/Scene/Components/BoneComponent.h @@ -0,0 +1,30 @@ +#pragma once +#include "src/Core/Core.h" +#include "src/Resource/Serializable.h" + + +namespace Nuake +{ + class BoneComponent + { + public: + BoneComponent() = default; + ~BoneComponent() = default; + + std::string Name; + + json Serialize() + { + BEGIN_SERIALIZE(); + SERIALIZE_VAL(Name); + END_SERIALIZE(); + } + + bool Deserialize(const json& j) + { + Name = j["Name"]; + + return true; + } + }; +} \ No newline at end of file diff --git a/Nuake/src/Scene/Components/SkinnedModelComponent.h b/Nuake/src/Scene/Components/SkinnedModelComponent.h index e536275f..0e6bdb53 100644 --- a/Nuake/src/Scene/Components/SkinnedModelComponent.h +++ b/Nuake/src/Scene/Components/SkinnedModelComponent.h @@ -24,7 +24,11 @@ namespace Nuake { BEGIN_SERIALIZE(); SERIALIZE_VAL(ModelPath); - SERIALIZE_OBJECT(ModelResource); + + if (ModelResource) + { + SERIALIZE_OBJECT(ModelResource); + } END_SERIALIZE(); } diff --git a/Nuake/src/Scene/Entities/Entity.cpp b/Nuake/src/Scene/Entities/Entity.cpp index 2e1b4928..3b6f7c25 100644 --- a/Nuake/src/Scene/Entities/Entity.cpp +++ b/Nuake/src/Scene/Entities/Entity.cpp @@ -16,6 +16,8 @@ #include "src/Scene/Components/CapsuleColliderComponent.h" #include "src/Scene/Components/SpriteComponent.h" #include "src/Scene/Components/ParticleEmitterComponent.h" +#include "src/Scene/Components/BoneComponent.h" +#include "src/Scene/Components/SkinnedModelComponent.h" namespace Nuake { @@ -69,7 +71,10 @@ namespace Nuake SERIALIZE_OBJECT_REF_LBL("QuakeMapComponent", GetComponent()) if (HasComponent()) SERIALIZE_OBJECT_REF_LBL("RigidBodyComponent", GetComponent()) - + if (HasComponent()) + SERIALIZE_OBJECT_REF_LBL("SkinnedModelComponent", GetComponent()) + if (HasComponent()) + SERIALIZE_OBJECT_REF_LBL("BoneComponent", GetComponent()) END_SERIALIZE(); } @@ -98,6 +103,8 @@ namespace Nuake DESERIALIZE_COMPONENT(SphereColliderComponent) DESERIALIZE_COMPONENT(RigidBodyComponent) DESERIALIZE_COMPONENT(BSPBrushComponent) + DESERIALIZE_COMPONENT(BoneComponent) + DESERIALIZE_COMPONENT(SkinnedModelComponent) return true; } diff --git a/Nuake/src/Scene/Entities/Entity.h b/Nuake/src/Scene/Entities/Entity.h index f51a0837..461b84ed 100644 --- a/Nuake/src/Scene/Entities/Entity.h +++ b/Nuake/src/Scene/Entities/Entity.h @@ -18,7 +18,7 @@ namespace Nuake void AddChild(Entity ent); int GetHandle() const { return (int)m_EntityHandle; } - int GetID() { return GetComponent().ID; } + int GetID() const { return GetComponent().ID; } template bool HasComponent() const @@ -51,6 +51,13 @@ namespace Nuake return component; } + template + T GetComponent() const + { + T component = m_Scene->m_Registry.get(m_EntityHandle); + return component; + } + void Destroy() { m_Scene->m_Registry.destroy(m_EntityHandle); diff --git a/Nuake/src/Scene/Lighting/Environment.h b/Nuake/src/Scene/Lighting/Environment.h index 33918997..55e41f07 100644 --- a/Nuake/src/Scene/Lighting/Environment.h +++ b/Nuake/src/Scene/Lighting/Environment.h @@ -42,6 +42,8 @@ namespace Nuake bool SSAOEnabled = true; Scope mSSAO; + bool SSREnabled = false; + Vector3 ClearColor; glm::vec4 m_AmbientColor; diff --git a/Nuake/src/Scene/Scene.cpp b/Nuake/src/Scene/Scene.cpp index 58750a01..59fb078d 100644 --- a/Nuake/src/Scene/Scene.cpp +++ b/Nuake/src/Scene/Scene.cpp @@ -4,6 +4,7 @@ #include "src/Scene/Systems/TransformSystem.h" #include "src/Scene/Systems/QuakeMapBuilder.h" #include "src/Scene/Systems/ParticleSystem.h" +#include "src/Scene/Systems/AnimationSystem.h" #include "src/Rendering/SceneRenderer.h" #include "Scene.h" @@ -24,12 +25,14 @@ #include "src/Scene/Components/WrenScriptComponent.h" #include "src/Scene/Components/BSPBrushComponent.h" #include "src/Scene/Components/InterfaceComponent.h" +#include "src/Scene/Components/SkinnedModelComponent.h" #include #include #include #include #include "src/Core/OS.h" +#include "Components/BoneComponent.h" namespace Nuake { @@ -47,6 +50,7 @@ namespace Nuake // Adding systems - Order is important m_Systems.push_back(CreateRef(this)); m_Systems.push_back(CreateRef(this)); + m_Systems.push_back(CreateRef(this)); m_Systems.push_back(CreateRef(this)); m_Systems.push_back(CreateRef(this)); @@ -190,14 +194,20 @@ namespace Nuake Entity Scene::GetEntity(const std::string& name) { - std::vector allEntities; - const auto& view = m_Registry.view(); + if (m_EntitiesNameMap.find(name) != m_EntitiesNameMap.end()) + { + return m_EntitiesNameMap[name]; + } + + const auto& view = m_Registry.view(); for (auto e : view) { - const auto& namec = view.get(e); + const auto& [namec] = view.get(e); if (namec.Name == name) { - return Entity{ e, this }; + auto entity = Entity{ e, this }; + m_EntitiesNameMap[name] = entity; + return entity; } } @@ -212,7 +222,7 @@ namespace Nuake std::string Scene::GetUniqueEntityName(const std::string& name) { std::string entityName; - if (GetEntity(name) == Entity()) + if (!EntityExists(name)) { return name; } @@ -256,6 +266,7 @@ namespace Nuake nameComponent.ID = id; m_EntitiesIDMap[id] = entity; + m_EntitiesNameMap[entityName] = entity; Logger::Log("Entity created with name: " + nameComponent.Name, "scene", LOG_TYPE::VERBOSE); return entity; @@ -284,10 +295,20 @@ namespace Nuake m_EntitiesIDMap.erase(entity.GetComponent().ID); } + if (m_EntitiesNameMap.find(entity.GetComponent().Name) != m_EntitiesNameMap.end()) + { + m_EntitiesNameMap.erase(entity.GetComponent().Name); + } + entity.Destroy(); m_Registry.shrink_to_fit(); } + bool Scene::EntityExists(const std::string& name) + { + return GetEntity(name).GetHandle() != -1; + } + Ref Scene::GetCurrentCamera() { if (Engine::IsPlayMode()) @@ -448,4 +469,64 @@ namespace Nuake return true; } + + void Scene::CreateSkeleton(Entity& entity) + { + // We cannot create a component if the entity doesn't have a skinned model + if (!entity.HasComponent()) + { + const std::string msg = "Cannot create a skeleton on entity: " + std::to_string(entity.GetID()); + Logger::Log(msg); + return; + } + + auto& component = entity.GetComponent(); + auto& skeletonRoot = component.ModelResource->GetSkeletonRootNode(); + + Entity skeletonRootEntity = CreateEntity(skeletonRoot.Name); + skeletonRootEntity.AddComponent().Name = skeletonRoot.Name; + skeletonRoot.EntityHandle = skeletonRootEntity.GetHandle(); + entity.AddChild(skeletonRootEntity); + + + Vector3 bonePosition; + Quat boneRotation; + Vector3 boneScale; + Decompose(skeletonRoot.Transform, bonePosition, boneRotation, boneScale); + + auto& transformComponent = skeletonRootEntity.GetComponent(); + transformComponent.SetLocalPosition(bonePosition); + transformComponent.SetLocalRotation(boneRotation); + transformComponent.SetLocalScale(boneScale); + transformComponent.SetLocalTransform(skeletonRoot.Transform); + + CreateSkeletonTraverse(skeletonRootEntity, skeletonRoot); + } + + void Scene::CreateSkeletonTraverse(Entity& entity, SkeletonNode& skeletonNode) + { + for (auto& c : skeletonNode.Children) + { + Entity boneEntity = CreateEntity(c.Name); + boneEntity.AddComponent(); + entity.AddChild(boneEntity); + + c.EntityHandle = boneEntity.GetHandle(); + + Vector3 bonePosition; + Quat boneRotation; + Vector3 boneScale; + Decompose(c.Transform, bonePosition, boneRotation, boneScale); + + auto& transformComponent = boneEntity.GetComponent(); + transformComponent.SetLocalPosition(bonePosition); + transformComponent.SetLocalRotation(boneRotation); + transformComponent.SetLocalScale(boneScale); + transformComponent.SetLocalTransform(c.Transform); + transformComponent.Dirty = false; + + CreateSkeletonTraverse(boneEntity, c); + } + } + } diff --git a/Nuake/src/Scene/Scene.h b/Nuake/src/Scene/Scene.h index 33e03ac2..cd9afff6 100644 --- a/Nuake/src/Scene/Scene.h +++ b/Nuake/src/Scene/Scene.h @@ -17,6 +17,7 @@ namespace Nuake { + class SkeletonNode; class Entity; class SceneRenderer; @@ -37,7 +38,8 @@ namespace Nuake public: Ref m_EditorCamera; entt::registry m_Registry; - std::map m_EntitiesIDMap; + std::unordered_map m_EntitiesIDMap; + std::unordered_map m_EntitiesNameMap; std::string Path = ""; SceneRenderer* m_SceneRenderer; @@ -65,6 +67,7 @@ namespace Nuake Entity CreateEntity(const std::string& name); Entity CreateEntity(const std::string& name, int id); void DestroyEntity(Entity entity); + bool EntityExists(const std::string& name); std::vector GetAllEntities(); Entity GetEntity(const std::string& name); @@ -89,5 +92,11 @@ namespace Nuake json Serialize() override; bool Deserialize(const json& j) override; + + // Component specific utilies + void CreateSkeleton(Entity& entity); + + private: + void CreateSkeletonTraverse(Entity& entity, SkeletonNode& skeletonNode); }; } diff --git a/Nuake/src/Scene/Systems/AnimationSystem.cpp b/Nuake/src/Scene/Systems/AnimationSystem.cpp new file mode 100644 index 00000000..75e8a4a2 --- /dev/null +++ b/Nuake/src/Scene/Systems/AnimationSystem.cpp @@ -0,0 +1,101 @@ +#include "AnimationSystem.h" + +#include "src/Scene/Scene.h" +#include "src/Scene/Entities/Entity.h" +#include "src/Scene/Components/SkinnedModelComponent.h" +#include +#include + +namespace Nuake +{ + AnimationSystem::AnimationSystem(Scene* scene) + { + m_Scene = scene; + } + + bool AnimationSystem::Init() + { + return true; + } + + void AnimationSystem::Update(Timestep ts) + { + auto view = m_Scene->m_Registry.view(); + for (auto e : view) + { + auto [transformComponent, skinnedComponent] = view.get(e); + + auto& model = skinnedComponent.ModelResource; + if (!model) + { + continue; + } + + Ref animation = model->GetCurrentAnimation(); + if (animation && model->IsPlaying) + { + float newAnimationTime = animation->GetCurrentTime() + (ts * animation->GetTicksPerSecond()); + animation->SetCurrentTime(newAnimationTime); + + auto& rootBone = model->GetSkeletonRootNode(); + UpdateBonePositionTraversal(rootBone, animation, animation->GetCurrentTime()); + } + } + } + + void AnimationSystem::UpdateBonePositionTraversal(SkeletonNode& bone, Ref animation, float time) + { + const std::string& boneName = bone.Name; + + auto& animationTrack = animation->GetTrack(boneName); + + Entity& boneEntity = m_Scene->GetEntity(boneName); + Entity& boneEnt = Entity{ (entt::entity)bone.EntityHandle, m_Scene }; + ///assert(boneEnt.GetHandle() == boneEntity.GetHandle()); + if (boneEnt.GetHandle() != 0) + { + auto& transformComponent = boneEnt.GetComponent(); + bone.FinalTransform = transformComponent.GetGlobalTransform() * bone.Offset; + + //if (!animationTrack.IsEmpty()) + { + // Get Update transform + animationTrack.Update(time); + + const Matrix4& finalTransform = animationTrack.GetFinalTransform(); + + Vector3 localPosition; + Quat localRotation; + Vector3 localScale; + Decompose(finalTransform, localPosition, localRotation, localScale); + + transformComponent.SetLocalPosition(localPosition); + transformComponent.SetLocalRotation(localRotation); + transformComponent.SetLocalScale(localScale); + transformComponent.SetLocalTransform(finalTransform); + transformComponent.Dirty = false; + } + } + + for (auto& childBone : bone.Children) + { + UpdateBonePositionTraversal(childBone, animation, time); + } + } + + + void AnimationSystem::FixedUpdate(Timestep ts) + { + + } + + void AnimationSystem::EditorUpdate() + { + + } + + void AnimationSystem::Exit() + { + + } +} diff --git a/Nuake/src/Scene/Systems/AnimationSystem.h b/Nuake/src/Scene/Systems/AnimationSystem.h new file mode 100644 index 00000000..57361dad --- /dev/null +++ b/Nuake/src/Scene/Systems/AnimationSystem.h @@ -0,0 +1,24 @@ +#pragma once +#include +#include + +#include "src/Resource/SkeletonNode.h" +#include "src/Resource/SkeletalAnimation.h" + +namespace Nuake +{ + class AnimationSystem : public System + { + public: + AnimationSystem(Scene* scene); + bool Init() override; + void Update(Timestep ts) override; + void Draw() override {} + void EditorUpdate() override; + void FixedUpdate(Timestep ts) override; + void Exit() override; + + private: + void UpdateBonePositionTraversal(SkeletonNode& bone, Ref animation, float time); + }; +} diff --git a/Nuake/src/Scene/Systems/PhysicsSystem.cpp b/Nuake/src/Scene/Systems/PhysicsSystem.cpp index e324500b..dba4dcf4 100644 --- a/Nuake/src/Scene/Systems/PhysicsSystem.cpp +++ b/Nuake/src/Scene/Systems/PhysicsSystem.cpp @@ -32,19 +32,6 @@ namespace Nuake InitializeCharacterControllers(); InitializeQuakeMap(); - // TODO: Triggers - - //auto bspTriggerView = m_Scene->m_Registry.view(); - //for (auto e : bspTriggerView) - //{ - // auto [transform, brush, trigger] = bspTriggerView.get(e); - - // Ref meshShape = CreateRef(brush.Meshes[0]); - // Ref ghostBody = CreateRef(transform.GetGlobalPosition(), meshShape); - // trigger.GhostObject = ghostBody; - - // PhysicsManager::Get()->RegisterGhostBody(ghostBody); - //} Logger::Log("Physic system initialized successfully"); return true; } @@ -56,6 +43,10 @@ namespace Nuake return; } + ApplyForces(); + + PhysicsManager::Get().Step(ts); + auto brushes = m_Scene->m_Registry.view(); for (auto e : brushes) { @@ -121,9 +112,7 @@ namespace Nuake return; InitializeRigidbodies(); - ApplyForces(); - - PhysicsManager::Get().Step(ts); + } void PhysicsSystem::Exit() diff --git a/Nuake/src/Scene/Systems/TransformSystem.cpp b/Nuake/src/Scene/Systems/TransformSystem.cpp index 8569987b..9986e892 100644 --- a/Nuake/src/Scene/Systems/TransformSystem.cpp +++ b/Nuake/src/Scene/Systems/TransformSystem.cpp @@ -78,20 +78,33 @@ namespace Nuake Quat globalOrientation = transform.GetLocalRotation(); Vector3 globalScale = transform.GetLocalScale(); +#ifndef FRAME_PERFECT_TRANSFORM ParentComponent parentComponent = currentParent.GetComponent(); - while (parentComponent.HasParent) + if (parentComponent.HasParent) { TransformComponent& transformComponent = parentComponent.Parent.GetComponent(); - globalPosition = transformComponent.GetLocalPosition() + (globalPosition); + globalPosition = transformComponent.GetGlobalPosition() + (globalPosition); + globalScale *= transformComponent.GetGlobalScale(); + globalOrientation = transformComponent.GetGlobalRotation() * globalOrientation; + globalTransform = transformComponent.GetGlobalTransform() * globalTransform; + } +#else + while (parentComponent.HasParent) + { + TransformComponent& transformComponent = parentComponent.Parent.GetComponent(); + + globalPosition = transformComponent.GetLocalPosition() + (globalPosition); + globalScale *= transformComponent.GetLocalScale(); globalOrientation = transformComponent.GetLocalRotation() * globalOrientation; globalTransform = transformComponent.GetLocalTransform() * globalTransform; - + NameComponent& nameComponent = parentComponent.Parent.GetComponent(); parentComponent = parentComponent.Parent.GetComponent(); } +#endif // FRAME_PERFECT_TRANSFORM transform.SetGlobalPosition(globalPosition); transform.SetGlobalRotation(globalOrientation); diff --git a/Nuake/src/Window.cpp b/Nuake/src/Window.cpp index 5bc39d31..07af332e 100644 --- a/Nuake/src/Window.cpp +++ b/Nuake/src/Window.cpp @@ -67,7 +67,7 @@ namespace Nuake SetWindowIcon("resources/Images/nuake-logo.png"); glfwMakeContextCurrent(m_Window); - SetVSync(true); + SetVSync(false); Logger::Log("Driver detected " + std::string(((char*)glGetString(GL_VERSION))), "renderer"); @@ -267,7 +267,6 @@ namespace Nuake ImGuiStyle& s = ImGui::GetStyle(); s.WindowMenuButtonPosition = ImGuiDir_None; - s.FrameRounding = 2.0f; s.GrabRounding = 2.0f; s.CellPadding = ImVec2(8, 8); s.WindowPadding = ImVec2(2, 2); @@ -277,7 +276,7 @@ namespace Nuake s.TabRounding = 0; s.WindowRounding = 0; s.ChildRounding = 0; - s.FrameRounding = 0; + s.FrameRounding = 4.0f; s.GrabRounding = 0; s.FramePadding = ImVec2(8, 4); s.ItemSpacing = ImVec2(8, 4); @@ -286,6 +285,8 @@ namespace Nuake s.WindowBorderSize = 0.0f; s.IndentSpacing = 12.0f; s.ChildBorderSize = 0.0f; + s.PopupRounding = 4.0f; + s.FrameBorderSize = 1.0f; ImVec4* colors = ImGui::GetStyle().Colors; colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f);