From 5b3221bdbcd2c7d7091af6df13082a95b1ac3f7a Mon Sep 17 00:00:00 2001 From: Mikulas Florek Date: Fri, 20 Mar 2020 20:47:31 +0100 Subject: [PATCH] cleanup; fixed shutdown; improved anim editor UI --- src/animation/animation_scene.cpp | 43 +- src/animation/animation_scene.h | 33 +- src/animation/controller.cpp | 2 + src/animation/editor/controller_editor.cpp | 1009 +++++++++++--------- src/animation/editor/controller_editor.h | 12 +- src/animation/editor/plugins.cpp | 10 +- src/animation/nodes.cpp | 2 +- src/editor/profiler_ui.cpp | 2 +- src/editor/settings.cpp | 2 +- src/gui/editor/plugins.cpp | 2 +- src/renderer/gpu/gpu.cpp | 2 +- 11 files changed, 644 insertions(+), 475 deletions(-) diff --git a/src/animation/animation_scene.cpp b/src/animation/animation_scene.cpp index fe91a9c53..54972f458 100644 --- a/src/animation/animation_scene.cpp +++ b/src/animation/animation_scene.cpp @@ -446,6 +446,10 @@ struct AnimationSceneImpl final : AnimationScene } } + Anim::Controller* getAnimatorController(EntityRef entity) override { + const Animator& animator = m_animators[m_animator_map[entity]]; + return animator.resource; + } Path getAnimatorSource(EntityRef entity) override { @@ -545,8 +549,8 @@ struct AnimationSceneImpl final : AnimationScene void setAnimatorInput(EntityRef entity, u32 input_idx, float value) override { Animator& animator = m_animators[m_animator_map[entity]]; const Anim::InputDecl& decl = animator.resource->m_inputs; - ASSERT(input_idx >= lengthOf(decl.inputs)); - ASSERT(decl.inputs[input_idx].type != Anim::InputDecl::FLOAT); + ASSERT(input_idx < lengthOf(decl.inputs)); + ASSERT(decl.inputs[input_idx].type == Anim::InputDecl::FLOAT); *(float*)&animator.ctx->inputs[decl.inputs[input_idx].offset] = value; } @@ -554,17 +558,44 @@ struct AnimationSceneImpl final : AnimationScene void setAnimatorInput(EntityRef entity, u32 input_idx, bool value) override { Animator& animator = m_animators[m_animator_map[entity]]; const Anim::InputDecl& decl = animator.resource->m_inputs; - ASSERT(input_idx >= lengthOf(decl.inputs)); - ASSERT(decl.inputs[input_idx].type != Anim::InputDecl::BOOL); + ASSERT(input_idx < lengthOf(decl.inputs)); + ASSERT(decl.inputs[input_idx].type == Anim::InputDecl::BOOL); *(bool*)&animator.ctx->inputs[decl.inputs[input_idx].offset] = value; } + float getAnimatorFloatInput(EntityRef entity, u32 input_idx) override { + Animator& animator = m_animators[m_animator_map[entity]]; + const Anim::InputDecl& decl = animator.resource->m_inputs; + ASSERT(input_idx < lengthOf(decl.inputs)); + ASSERT(decl.inputs[input_idx].type == Anim::InputDecl::FLOAT); + + return *(float*)&animator.ctx->inputs[decl.inputs[input_idx].offset]; + } + + bool getAnimatorBoolInput(EntityRef entity, u32 input_idx) override { + Animator& animator = m_animators[m_animator_map[entity]]; + const Anim::InputDecl& decl = animator.resource->m_inputs; + ASSERT(input_idx < lengthOf(decl.inputs)); + ASSERT(decl.inputs[input_idx].type == Anim::InputDecl::BOOL); + + return *(bool*)&animator.ctx->inputs[decl.inputs[input_idx].offset]; + } + + u32 getAnimatorU32Input(EntityRef entity, u32 input_idx) override { + Animator& animator = m_animators[m_animator_map[entity]]; + const Anim::InputDecl& decl = animator.resource->m_inputs; + ASSERT(input_idx < lengthOf(decl.inputs)); + ASSERT(decl.inputs[input_idx].type == Anim::InputDecl::U32); + + return *(u32*)&animator.ctx->inputs[decl.inputs[input_idx].offset]; + } + void setAnimatorInput(EntityRef entity, u32 input_idx, u32 value) override { Animator& animator = m_animators[m_animator_map[entity]]; const Anim::InputDecl& decl = animator.resource->m_inputs; - ASSERT(input_idx >= lengthOf(decl.inputs)); - ASSERT(decl.inputs[input_idx].type != Anim::InputDecl::U32); + ASSERT(input_idx < lengthOf(decl.inputs)); + ASSERT(decl.inputs[input_idx].type == Anim::InputDecl::U32); *(u32*)&animator.ctx->inputs[decl.inputs[input_idx].offset] = value; } diff --git a/src/animation/animation_scene.h b/src/animation/animation_scene.h index 9024cd9ac..777320a76 100644 --- a/src/animation/animation_scene.h +++ b/src/animation/animation_scene.h @@ -1,43 +1,28 @@ #pragma once - #include "engine/lumix.h" #include "engine/plugin.h" - struct lua_State; - namespace Lumix { -struct Animation; -struct IAllocator; -struct OutputMemoryStream; -struct Path; +namespace Anim { struct Controller; } -namespace Anim -{ -struct Controller; -} - - -struct Animable -{ +struct Animable { Time time; - Animation* animation; + struct Animation* animation; EntityRef entity; }; - -struct AnimationScene : IScene -{ - static AnimationScene* create(Engine& engine, IPlugin& plugin, Universe& universe, IAllocator& allocator); +struct AnimationScene : IScene { + static AnimationScene* create(Engine& engine, IPlugin& plugin, Universe& universe, struct IAllocator& allocator); static void destroy(AnimationScene& scene); static void registerLuaAPI(lua_State* L); - virtual const OutputMemoryStream& getEventStream() const = 0; - virtual Path getPropertyAnimation(EntityRef entity) = 0; + virtual const struct OutputMemoryStream& getEventStream() const = 0; + virtual struct Path getPropertyAnimation(EntityRef entity) = 0; virtual void setPropertyAnimation(EntityRef entity, const Path& path) = 0; virtual bool isPropertyAnimatorEnabled(EntityRef entity) = 0; virtual void enablePropertyAnimator(EntityRef entity, bool enabled) = 0; @@ -50,6 +35,9 @@ struct AnimationScene : IScene virtual void setAnimatorInput(EntityRef entity, u32 input_idx, u32 value) = 0; virtual void setAnimatorInput(EntityRef entity, u32 input_idx, float value) = 0; virtual void setAnimatorInput(EntityRef entity, u32 input_idx, bool value) = 0; + virtual float getAnimatorFloatInput(EntityRef entity, u32 input_idx) = 0; + virtual bool getAnimatorBoolInput(EntityRef entity, u32 input_idx) = 0; + virtual u32 getAnimatorU32Input(EntityRef entity, u32 input_idx) = 0; virtual struct LocalRigidTransform getAnimatorRootMotion(EntityRef entity) = 0; virtual void setAnimatorSource(EntityRef entity, const Path& path) = 0; virtual struct Path getAnimatorSource(EntityRef entity) = 0; @@ -57,6 +45,7 @@ struct AnimationScene : IScene virtual void applyAnimatorSet(EntityRef entity, u32 idx) = 0; virtual void setAnimatorDefaultSet(EntityRef entity, u32 idx) = 0; virtual u32 getAnimatorDefaultSet(EntityRef entity) = 0; + virtual Anim::Controller* getAnimatorController(EntityRef entity) = 0; virtual float getAnimationLength(int animation_idx) = 0; }; diff --git a/src/animation/controller.cpp b/src/animation/controller.cpp index 5fca65f0f..b78e6d087 100644 --- a/src/animation/controller.cpp +++ b/src/animation/controller.cpp @@ -35,6 +35,7 @@ void Controller::unload() { void Controller::initEmpty() { ASSERT(!m_root); m_root = LUMIX_NEW(m_allocator, GroupNode)(nullptr, m_allocator); + m_root->m_name = "Root"; } bool Controller::load(u64 size, const u8* mem) { @@ -63,6 +64,7 @@ void Controller::destroyRuntime(RuntimeContext& ctx) { RuntimeContext* Controller::createRuntime(u32 anim_set) { RuntimeContext* ctx = LUMIX_NEW(m_allocator, RuntimeContext)(*this, m_allocator); ctx->inputs.resize(computeInputsSize(*this)); + memset(ctx->inputs.begin(), 0, ctx->inputs.byte_size()); ctx->animations.resize(m_animation_slots.size()); memset(ctx->animations.begin(), 0, ctx->animations.byte_size()); for (AnimationEntry& anim : m_animation_entries) { diff --git a/src/animation/editor/controller_editor.cpp b/src/animation/editor/controller_editor.cpp index 1a613987e..4e703f4e3 100644 --- a/src/animation/editor/controller_editor.cpp +++ b/src/animation/editor/controller_editor.cpp @@ -1,4 +1,5 @@ #include +#include #include "animation/animation_scene.h" #include "controller_editor.h" @@ -18,516 +19,666 @@ namespace Lumix::Anim { +struct ControllerEditorImpl : ControllerEditor { + ControllerEditorImpl(StudioApp& app) + : m_app(app) + { + IAllocator& allocator = app.getAllocator(); + ResourceManager* res_manager = app.getEngine().getResourceManager().get(Controller::TYPE); + ASSERT(res_manager); -ControllerEditor::ControllerEditor(StudioApp& app) - : m_app(app) -{ - IAllocator& allocator = app.getAllocator(); - ResourceManager* res_manager = app.getEngine().getResourceManager().get(Controller::TYPE); - ASSERT(res_manager); - - m_controller = LUMIX_NEW(allocator, Controller)(Path("anim_editor"), *res_manager, allocator); - m_controller->initEmpty(); - m_current_level = m_controller->m_root; -} - -ControllerEditor::~ControllerEditor() { - IAllocator& allocator = m_app.getAllocator(); - m_controller->destroy(); - LUMIX_DELETE(allocator, m_controller); -} - -static void createChild(GroupNode& parent, Node::Type type, IAllocator& allocator) { - Node* node = nullptr; - switch(type) { - case Node::ANIMATION: node = LUMIX_NEW(allocator, AnimationNode)(&parent, allocator); break; - case Node::GROUP: node = LUMIX_NEW(allocator, GroupNode)(&parent, allocator); break; - case Node::BLEND1D: node = LUMIX_NEW(allocator, Blend1DNode)(&parent, allocator); break; - default: ASSERT(false); return; + m_controller = LUMIX_NEW(allocator, Controller)(Path("anim_editor"), *res_manager, allocator); + m_controller->initEmpty(); + m_current_node = m_controller->m_root; } - node->m_name = "new"; - parent.m_children.emplace(allocator); - parent.m_children.back().node = node; -} - -static void ui(Node& node, ControllerEditor& editor) { - char tmp[64]; - copyString(tmp, node.m_name.c_str()); - if (ImGui::InputText("Name", tmp, sizeof(tmp))) { - node.m_name = tmp; + ~ControllerEditorImpl() { + IAllocator& allocator = m_app.getAllocator(); + m_controller->destroy(); + LUMIX_DELETE(allocator, m_controller); } -} -static void ui(AnimationNode& node, ControllerEditor& editor) { - ui((Node&)node, editor); + static void createChild(GroupNode& parent, Node::Type type, IAllocator& allocator) { + Node* node = nullptr; + switch(type) { + case Node::ANIMATION: node = LUMIX_NEW(allocator, AnimationNode)(&parent, allocator); break; + case Node::GROUP: node = LUMIX_NEW(allocator, GroupNode)(&parent, allocator); break; + case Node::BLEND1D: node = LUMIX_NEW(allocator, Blend1DNode)(&parent, allocator); break; + default: ASSERT(false); return; + } - const Array& names = editor.m_controller->m_animation_slots; + node->m_name = "new"; + parent.m_children.emplace(allocator); + parent.m_children.back().node = node; + } + + void properties_ui(AnimationNode& node) { + const Array& names = m_controller->m_animation_slots; - const char* preview = node.m_slot < (u32)names.size() ? names[node.m_slot].c_str() : ""; - if (ImGui::BeginCombo("Animation", preview)) { - for (u32 i = 0; i < (u32)names.size(); ++i) { - if (ImGui::Selectable(names[i].c_str())) { - node.m_slot = i; - } - } - ImGui::EndCombo(); - } -} - -static void ui(GroupNode& node, ControllerEditor& editor) { - ui((Node&)node, editor); - if (ImGui::Button("View content")) { - editor.m_current_level = &node; - } -} - -static void ui(Blend1DNode& node, ControllerEditor& editor) { - ui((Node&)node, editor); - const InputDecl::Input& input = editor.m_controller->m_inputs.inputs[node.m_input_index]; - if (ImGui::BeginCombo("Input", input.name)) { - for (const InputDecl::Input& input : editor.m_controller->m_inputs.inputs) { - if (ImGui::Selectable(input.name)) { - node.m_input_index = u32(&input - editor.m_controller->m_inputs.inputs); - } - } - ImGui::EndCombo(); - } - - ImGui::Columns(2); - ImGui::Text("Value"); - ImGui::NextColumn(); - ImGui::Text("Slot"); - ImGui::NextColumn(); - ImGui::Separator(); - - for (Blend1DNode::Child& child : node.m_children) { - ImGui::PushID(&child); - - ImGui::PushItemWidth(-1); - ImGui::InputFloat("##val", &child.value); - ImGui::PopItemWidth(); - ImGui::NextColumn(); - - ImGui::PushItemWidth(-1); - - const Array& slots = editor.m_controller->m_animation_slots; - if (ImGui::BeginCombo("##anim", child.slot < (u32)slots.size() ? slots[child.slot].c_str() : "")) { - for (u32 i = 0; i < (u32)slots.size(); ++i) { - if (ImGui::Selectable(slots[i].c_str())) { - child.slot = i; + const char* preview = node.m_slot < (u32)names.size() ? names[node.m_slot].c_str() : ""; + if (ImGui::BeginCombo("Animation", preview)) { + for (u32 i = 0; i < (u32)names.size(); ++i) { + if (ImGui::Selectable(names[i].c_str())) { + node.m_slot = i; } } ImGui::EndCombo(); } - - ImGui::PopItemWidth(); + } + + void properties_ui(GroupNode& node) {} + + void properties_ui(Blend1DNode& node) { + const InputDecl::Input& input = m_controller->m_inputs.inputs[node.m_input_index]; + if (ImGui::BeginCombo("Input", input.name)) { + for (const InputDecl::Input& input : m_controller->m_inputs.inputs) { + if (ImGui::Selectable(input.name)) { + node.m_input_index = u32(&input - m_controller->m_inputs.inputs); + } + } + ImGui::EndCombo(); + } + + ImGui::Columns(2); + ImGui::Text("Value"); ImGui::NextColumn(); + ImGui::Text("Slot"); + ImGui::NextColumn(); + ImGui::Separator(); + + for (Blend1DNode::Child& child : node.m_children) { + ImGui::PushID(&child); + + ImGui::PushItemWidth(-1); + ImGui::InputFloat("##val", &child.value); + ImGui::PopItemWidth(); + ImGui::NextColumn(); + + ImGui::PushItemWidth(-1); + + const Array& slots = m_controller->m_animation_slots; + if (ImGui::BeginCombo("##anim", child.slot < (u32)slots.size() ? slots[child.slot].c_str() : "")) { + for (u32 i = 0; i < (u32)slots.size(); ++i) { + if (ImGui::Selectable(slots[i].c_str())) { + child.slot = i; + } + } + ImGui::EndCombo(); + } + + ImGui::PopItemWidth(); + ImGui::NextColumn(); - ImGui::PopID(); - } - ImGui::Columns(); - if (ImGui::Button("Add")) { - node.m_children.emplace(); - if(node.m_children.size() > 1) { - node.m_children.back().value = node.m_children[node.m_children.size() - 2].value; + ImGui::PopID(); + } + ImGui::Columns(); + if (ImGui::Button("Add")) { + node.m_children.emplace(); + if(node.m_children.size() > 1) { + node.m_children.back().value = node.m_children[node.m_children.size() - 2].value; + } } } -} -static void ui_dispatch(Node& node, ControllerEditor& editor) { - switch(node.type()) { - case Node::ANIMATION: ui((AnimationNode&)node, editor); break; - case Node::GROUP: ui((GroupNode&)node, editor); break; - case Node::BLEND1D: ui((Blend1DNode&)node, editor); break; - default: ASSERT(false); break; - } -} - -static bool canLoadFromEntity(StudioApp& app) { - const Array& selected = app.getWorldEditor().getSelectedEntities(); - if (selected.size() != 1) return false; - return app.getWorldEditor().getUniverse()->hasComponent(selected[0], Reflection::getComponentType("animator")); -} - -static const char* getPathFromEntity(StudioApp& app) { - const Array& selected = app.getWorldEditor().getSelectedEntities(); - if (selected.size() != 1) return ""; - Universe* universe = app.getWorldEditor().getUniverse(); - const ComponentType cmp_type = Reflection::getComponentType("animator"); - if (!universe->hasComponent(selected[0], cmp_type)) return ""; - AnimationScene* scene = (AnimationScene*)universe->getScene(cmp_type); - return scene->getAnimatorSource(selected[0]).c_str(); -} - -static void load(ControllerEditor& editor, const char* path) { - OS::InputFile file; - if (file.open(path)) { - IAllocator& allocator = editor.m_app.getAllocator(); - ResourceManager* res_manager = editor.m_app.getEngine().getResourceManager().get(Controller::TYPE); - Array data(allocator); - data.resize((u32)file.size()); - if (!file.read(data.begin(), data.byte_size())) { - logError("Animation") << "Failed to read " << path; + void child_properties_ui(Node& node) { + if (node.m_parent && node.m_parent->type() == Node::Type::GROUP) { + GroupNode* group = (GroupNode*)node.m_parent; + for (GroupNode::Child& c : group->m_children) { + if (c.node != &node) continue; + + conditionInput("Condition", m_controller->m_inputs, Ref(c.condition_str), Ref(c.condition)); + break; + } } - else { - InputMemoryStream str(data.begin(), data.byte_size()); - Controller* new_controller = LUMIX_NEW(allocator, Controller)(Path("anim_editor"), *res_manager, allocator); - if (new_controller ->deserialize(str)) { - LUMIX_DELETE(allocator, editor.m_controller); - editor.m_controller = new_controller; - editor.m_current_level = editor.m_controller->m_root; + } + + void ui_dispatch(Node& node) { + char tmp[64]; + copyString(tmp, node.m_name.c_str()); + if (ImGui::InputText("Name", tmp, sizeof(tmp))) { + node.m_name = tmp; + } + + child_properties_ui(node); + + switch(node.type()) { + case Node::ANIMATION: properties_ui((AnimationNode&)node); break; + case Node::GROUP: properties_ui((GroupNode&)node); break; + case Node::BLEND1D: properties_ui((Blend1DNode&)node); break; + default: ASSERT(false); break; + } + } + + static bool canLoadFromEntity(StudioApp& app) { + const Array& selected = app.getWorldEditor().getSelectedEntities(); + if (selected.size() != 1) return false; + return app.getWorldEditor().getUniverse()->hasComponent(selected[0], Reflection::getComponentType("animator")); + } + + static const char* getPathFromEntity(StudioApp& app) { + const Array& selected = app.getWorldEditor().getSelectedEntities(); + if (selected.size() != 1) return ""; + Universe* universe = app.getWorldEditor().getUniverse(); + const ComponentType cmp_type = Reflection::getComponentType("animator"); + if (!universe->hasComponent(selected[0], cmp_type)) return ""; + AnimationScene* scene = (AnimationScene*)universe->getScene(cmp_type); + return scene->getAnimatorSource(selected[0]).c_str(); + } + + void load(ControllerEditor& editor, const char* path) { + OS::InputFile file; + if (file.open(path)) { + IAllocator& allocator = m_app.getAllocator(); + ResourceManager* res_manager = m_app.getEngine().getResourceManager().get(Controller::TYPE); + Array data(allocator); + data.resize((u32)file.size()); + if (!file.read(data.begin(), data.byte_size())) { + logError("Animation") << "Failed to read " << path; } else { - LUMIX_DELETE(allocator, new_controller); + InputMemoryStream str(data.begin(), data.byte_size()); + Controller* new_controller = LUMIX_NEW(allocator, Controller)(Path("anim_editor"), *res_manager, allocator); + if (new_controller ->deserialize(str)) { + LUMIX_DELETE(allocator, m_controller); + m_controller = new_controller; + m_current_node = m_controller->m_root; + } + else { + LUMIX_DELETE(allocator, new_controller); + } } + file.close(); + } + else { + logError("Animation") << "Failed to open " << path; } - file.close(); - } - else { - logError("Animation") << "Failed to open " << path; - } -} - -static bool conditionInput(const char* label, Anim::InputDecl& input, Ref condition_str, Ref condition) { - char tmp[1024]; - copyString(tmp, condition_str->c_str()); - if (ImGui::InputText(label, tmp, sizeof(tmp), ImGuiInputTextFlags_EnterReturnsTrue)) { - condition_str = tmp; - condition->compile(tmp, input); - return true; } - return false; -} - -static bool nodeInput(const char* label, Ref value, const Array& children) { - if (!ImGui::BeginCombo(label, children[value].node->m_name.c_str())) return false; - - for (GroupNode::Child& child : children) { - if (ImGui::Selectable(child.node->m_name.c_str())) { - value = u32(&child - children.begin()); + static bool conditionInput(const char* label, InputDecl& input, Ref condition_str, Ref condition) { + char tmp[1024]; + copyString(tmp, condition_str->c_str()); + if (ImGui::InputText(label, tmp, sizeof(tmp), ImGuiInputTextFlags_EnterReturnsTrue)) { + condition_str = tmp; + condition->compile(tmp, input); return true; } + + return false; } - ImGui::EndCombo(); - return false; -} + static bool nodeInput(const char* label, Ref value, const Array& children) { + if (!ImGui::BeginCombo(label, children[value].node->m_name.c_str())) return false; -void ControllerEditor::onWindowGUI() { - if (ImGui::Begin("Animation editor", nullptr, ImGuiWindowFlags_MenuBar)) { - if (ImGui::BeginMenuBar()) { - if (ImGui::BeginMenu("File")) { - if (ImGui::MenuItem("Save")) { - char path[MAX_PATH_LENGTH]; - if (OS::getSaveFilename(Span(path), "Animation controller\0*.act", "act")) { - OutputMemoryStream str(m_controller->m_allocator); - m_controller->serialize(str); - OS::OutputFile file; - if (file.open(path)) { - if (!file.write(str.getData(), str.getPos())) { - logError("Animation") << "Failed to write " << path; - } - file.close(); - } - else { - logError("Animation") << "Failed to create " << path; - } - } - } - if (ImGui::MenuItem("Load")) { - char path[MAX_PATH_LENGTH]; - - if (OS::getOpenFilename(Span(path), "Animation controller\0*.act", nullptr)) { - load(*this, path); - } - } - - if (ImGui::MenuItem("Load from entity", nullptr, false, canLoadFromEntity(m_app))) { - char tmp[MAX_PATH_LENGTH]; - copyString(tmp, m_app.getEngine().getFileSystem().getBasePath()); - catString(tmp, getPathFromEntity(m_app)); - load(*this, tmp); - } - ImGui::EndMenu(); + for (GroupNode::Child& child : children) { + if (ImGui::Selectable(child.node->m_name.c_str())) { + value = u32(&child - children.begin()); + return true; } - if (ImGui::BeginMenu("Nodes")) { - if (ImGui::BeginMenu("Create")) { - if (ImGui::MenuItem("Animation")) { - createChild(*m_current_level, Node::ANIMATION, m_controller->m_allocator); + } + + ImGui::EndCombo(); + return false; + } + + static const char* toString(Node::Type type) { + switch (type) { + case Node::Type::ANIMATION: return "Animation"; break; + case Node::Type::BLEND1D: return "Blend 1D"; break; + case Node::Type::GROUP: return "Group"; break; + case Node::Type::LAYERS: return "Layers"; break; + default: ASSERT(false); return "N/A"; break; + } + } + + static bool isContainer(const Node& node) { + switch (node.type()) { + case Node::Type::ANIMATION: + case Node::Type::BLEND1D: return false; + case Node::Type::GROUP: + case Node::Type::LAYERS: return true; + default: ASSERT(false); return false; + } + } + + void hierarchy_ui(Node& node) { + const bool is_container = isContainer(node); + bool is_parent_group = node.m_parent && node.m_parent->type() == Node::Type::GROUP; + bool is_layer = node.m_parent && node.m_parent->type() == Node::Type::LAYERS; + + ImGuiTreeNodeFlags flags = ImGuiTreeNodeFlags_DefaultOpen | ImGuiTreeNodeFlags_OpenOnArrow | (&node == m_current_node ? ImGuiTreeNodeFlags_Selected : 0); + if (!is_container) flags |= ImGuiTreeNodeFlags_Leaf; + + const char* type_str = toString(node.type()); + if (ImGui::TreeNodeEx(&node, flags, "%s (%s)", node.m_name.c_str(), type_str)) { + if (ImGui::IsMouseClicked(0) && ImGui::IsItemHovered()) { + m_current_node = &node; + } + + if (is_parent_group && ImGui::IsMouseClicked(1) && ImGui::IsItemHovered()) { + ImGui::OpenPopup("group_popup"); + } + if (is_layer && ImGui::IsMouseClicked(1) && ImGui::IsItemHovered()) { + ImGui::OpenPopup("layer_popup"); + } + + if (ImGui::BeginPopup("group_popup")) { + ImGui::TextUnformatted(node.m_name.c_str()); + ImGui::Separator(); + if (ImGui::Selectable("Remove")) { + if (m_current_node == &node) m_current_node = nullptr; + ((GroupNode*)node.m_parent)->m_children.eraseItems([&node](GroupNode::Child& c){ return c.node == &node; }); + LUMIX_DELETE(m_controller->m_allocator, &node); + ImGui::EndPopup(); + ImGui::TreePop(); + return; + } + ImGui::EndPopup(); + } + + switch (node.type()) { + case Node::Type::GROUP: { + GroupNode& group = (GroupNode&)node; + for (GroupNode::Child& c : group.m_children) { + hierarchy_ui(*c.node); } - if (ImGui::MenuItem("Blend 1D")) { - createChild(*m_current_level, Node::BLEND1D, m_controller->m_allocator); + break; + } + case Node::Type::LAYERS: { + LayersNode& layers = (LayersNode&)node; + for (LayersNode::Layer& l : layers.m_layers) { + hierarchy_ui(l.node); } - if (ImGui::MenuItem("Group")) { - createChild(*m_current_level, Node::GROUP, m_controller->m_allocator); + break; + } + } + ImGui::TreePop(); + } + } + + void debuggerUI() { + if (ImGui::Begin("Animation debugger")) { + const Array& selected = m_app.getWorldEditor().getSelectedEntities(); + if (selected.empty()) { + ImGui::End(); + return; + } + + Universe* universe = m_app.getWorldEditor().getUniverse(); + const ComponentType cmp_type = Reflection::getComponentType("animator"); + if (!universe->hasComponent(selected[0], cmp_type)) { + ImGui::End(); + return; + } + AnimationScene* scene = (AnimationScene*)universe->getScene(cmp_type); + Controller* ctrl = scene->getAnimatorController(selected[0]); + + for (const InputDecl::Input& input : ctrl->m_inputs.inputs) { + const u32 idx = u32(&input - ctrl->m_inputs.inputs); + switch (input.type) { + case InputDecl::Type::EMPTY: break; + case InputDecl::Type::FLOAT: { + float val = scene->getAnimatorFloatInput(selected[0], idx); + if (ImGui::DragFloat(input.name, &val)) { + scene->setAnimatorInput(selected[0], idx, val); + } + break; } - if (ImGui::MenuItem("Layers")) { - createChild(*m_current_level, Node::LAYERS, m_controller->m_allocator); + case InputDecl::Type::BOOL: { + bool val = scene->getAnimatorBoolInput(selected[0], idx); + if (ImGui::Checkbox(input.name, &val)) { + scene->setAnimatorInput(selected[0], idx, val); + } + break; + } + case InputDecl::Type::U32: { + u32 val = scene->getAnimatorU32Input(selected[0], idx); + if (ImGui::DragInt(input.name, (int*)&val, 1, 0, 0x7ffFFff)) { + scene->setAnimatorInput(selected[0], idx, val); + } + break; + } + default: ASSERT(false); break; + } + } + } + ImGui::End(); + } + + void onWindowGUI() override { + debuggerUI(); + + if (ImGui::Begin("Animation hierarchy", nullptr, ImGuiWindowFlags_MenuBar)) { + if (ImGui::BeginMenuBar()) { + if (ImGui::BeginMenu("File")) { + if (ImGui::MenuItem("Save")) { + char path[MAX_PATH_LENGTH]; + if (OS::getSaveFilename(Span(path), "Animation controller\0*.act", "act")) { + OutputMemoryStream str(m_controller->m_allocator); + m_controller->serialize(str); + OS::OutputFile file; + if (file.open(path)) { + if (!file.write(str.getData(), str.getPos())) { + logError("Animation") << "Failed to write " << path; + } + file.close(); + } + else { + logError("Animation") << "Failed to create " << path; + } + } + } + if (ImGui::MenuItem("Load")) { + char path[MAX_PATH_LENGTH]; + + if (OS::getOpenFilename(Span(path), "Animation controller\0*.act", nullptr)) { + load(*this, path); + } + } + + if (ImGui::MenuItem("Load from entity", nullptr, false, canLoadFromEntity(m_app))) { + char tmp[MAX_PATH_LENGTH]; + copyString(tmp, m_app.getEngine().getFileSystem().getBasePath()); + catString(tmp, getPathFromEntity(m_app)); + load(*this, tmp); } ImGui::EndMenu(); } - ImGui::EndMenu(); + + if (m_current_node && m_current_node->type() == Node::Type::GROUP) { + if (ImGui::BeginMenu("Create node")) { + GroupNode& group = (GroupNode&)*m_current_node; + if (ImGui::MenuItem("Animation")) createChild(group, Node::ANIMATION, m_controller->m_allocator); + if (ImGui::MenuItem("Blend1D")) createChild(group, Node::BLEND1D, m_controller->m_allocator); + if (ImGui::MenuItem("Group")) createChild(group, Node::GROUP, m_controller->m_allocator); + if (ImGui::MenuItem("Layers")) createChild(group, Node::LAYERS, m_controller->m_allocator); + ImGui::EndMenu(); + } + } + + /*if (ImGui::BeginMenu("Nodes")) { + if (ImGui::BeginMenu("Create")) { + if (ImGui::MenuItem("Animation")) { + createChild(*m_current_node, Node::ANIMATION, m_controller->m_allocator); + } + if (ImGui::MenuItem("Blend 1D")) { + createChild(*m_current_level, Node::BLEND1D, m_controller->m_allocator); + } + if (ImGui::MenuItem("Group")) { + createChild(*m_current_level, Node::GROUP, m_controller->m_allocator); + } + if (ImGui::MenuItem("Layers")) { + createChild(*m_current_level, Node::LAYERS, m_controller->m_allocator); + } + ImGui::EndMenu(); + } + ImGui::EndMenu(); + }*/ + + ImGui::EndMenuBar(); } - if (m_current_level->m_parent && ImGui::Button("Go up")) { - m_current_level = m_current_level->m_parent; - } - - ImGui::EndMenuBar(); + ImGui::Columns(2); + hierarchy_ui(*m_controller->m_root); + ImGui::NextColumn(); + if (m_current_node) ui_dispatch(*m_current_node); + ImGui::Columns(); } - - if (ImGui::CollapsingHeader("Inputs")) { - Anim::InputDecl& inputs = m_controller->m_inputs; - for (Anim::InputDecl::Input& input : inputs.inputs) { - if (input.type == Anim::InputDecl::Type::EMPTY) continue; + ImGui::End(); - if (ImGui::TreeNodeEx(&input, 0, "%s", input.name.data)) { - ImGui::InputText("Name", input.name.data, sizeof(input.name.data)); - if (ImGui::Combo("Type", (int*)&input.type, "float\0u32\0bool")) { + if (ImGui::Begin("Animation controller", nullptr)) { + if (ImGui::CollapsingHeader("Inputs")) { + InputDecl& inputs = m_controller->m_inputs; + + ImGui::Columns(2); + if (ImGui::Button(ICON_FA_PLUS_CIRCLE)) inputs.addInput(); + ImGui::SameLine(); + ImGui::TextUnformatted("Name"); ImGui::NextColumn(); + ImGui::TextUnformatted("Type"); ImGui::NextColumn(); + for (InputDecl::Input& input : inputs.inputs) { + if (input.type == InputDecl::Type::EMPTY) continue; + ImGui::PushID(&input); + if(ImGui::Button(ICON_FA_MINUS_CIRCLE)) inputs.removeInput(int(&input - inputs.inputs)); + ImGui::SameLine(); + ImGui::SetNextItemWidth(-1); + ImGui::InputText("##name", input.name.data, sizeof(input.name.data)); + ImGui::NextColumn(); + ImGui::SetNextItemWidth(-1); + if (ImGui::Combo("##type", (int*)&input.type, "float\0u32\0bool")) { inputs.recalculateOffsets(); } - if(ImGui::Button("Remove")) { - inputs.removeInput(int(&input - inputs.inputs)); + ImGui::NextColumn(); + ImGui::PopID(); + } + ImGui::Columns(); + } + + if (ImGui::CollapsingHeader("Slots")) { + for (u32 i = 0; i < (u32)m_controller->m_animation_slots.size(); ++i) { + String& slot = m_controller->m_animation_slots[i]; + char tmp[64]; + copyString(tmp, slot.c_str()); + ImGui::SetNextItemWidth(-1); + if (ImGui::InputText(StaticString<32>("##", (u64)(uintptr)&slot), tmp, sizeof(tmp))) { + // update AnimationNode::m_animation_hash + slot = tmp; } - ImGui::TreePop(); + } + if (ImGui::Button(ICON_FA_PLUS_CIRCLE "##create_slot")) { + m_controller->m_animation_slots.emplace("", m_controller->m_allocator); } } - if (ImGui::Button("Add")) inputs.addInput(); - } - if (ImGui::CollapsingHeader("Slots")) { - for (u32 i = 0; i < (u32)m_controller->m_animation_slots.size(); ++i) { - String& slot = m_controller->m_animation_slots[i]; - char tmp[64]; - copyString(tmp, slot.c_str()); - if (ImGui::InputText(StaticString<32>("##", (u64)(uintptr)&slot), tmp, sizeof(tmp))) { - // update AnimationNode::m_animation_hash - slot = tmp; - } - } - if (ImGui::Button("Create")) { - m_controller->m_animation_slots.emplace("", m_controller->m_allocator); - } - } - - if (ImGui::CollapsingHeader("Animations")) { - ImGui::Columns(3); - ImGui::Text("Set"); - ImGui::NextColumn(); - ImGui::Text("Slot"); - ImGui::NextColumn(); - ImGui::Text("Animation"); - ImGui::NextColumn(); - ImGui::Separator(); - for (u32 i = 0; i < (u32)m_controller->m_animation_entries.size(); ++i) { - ImGui::PushID(i); - Controller::AnimationEntry& entry = m_controller->m_animation_entries[i]; - ImGui::PushItemWidth(-1); - ImGui::InputInt("##set", (int*)&entry.set); - ImGui::PopItemWidth(); + if (ImGui::CollapsingHeader("Animations")) { + ImGui::Columns(3); + ImGui::Text("Set"); ImGui::NextColumn(); - const char* preview = entry.slot < (u32)m_controller->m_animation_slots.size() ? m_controller->m_animation_slots[entry.slot].c_str() : "N/A"; - ImGui::PushItemWidth(-1); - if (ImGui::BeginCombo("##slot", preview, 0)) { - for (u32 i = 0, c = m_controller->m_animation_slots.size(); i < c; ++i) { - if (ImGui::Selectable(m_controller->m_animation_slots[i].c_str())) { - entry.slot = i; + ImGui::Text("Slot"); + ImGui::NextColumn(); + ImGui::Text("Animation"); + ImGui::NextColumn(); + ImGui::Separator(); + for (u32 i = 0; i < (u32)m_controller->m_animation_entries.size(); ++i) { + ImGui::PushID(i); + Controller::AnimationEntry& entry = m_controller->m_animation_entries[i]; + ImGui::PushItemWidth(-1); + ImGui::InputInt("##set", (int*)&entry.set); + ImGui::PopItemWidth(); + ImGui::NextColumn(); + const char* preview = entry.slot < (u32)m_controller->m_animation_slots.size() ? m_controller->m_animation_slots[entry.slot].c_str() : "N/A"; + ImGui::PushItemWidth(-1); + if (ImGui::BeginCombo("##slot", preview, 0)) { + for (u32 i = 0, c = m_controller->m_animation_slots.size(); i < c; ++i) { + if (ImGui::Selectable(m_controller->m_animation_slots[i].c_str())) { + entry.slot = i; + } } + ImGui::EndCombo(); } - ImGui::EndCombo(); - } - ImGui::PopItemWidth(); - ImGui::NextColumn(); - ImGui::PushItemWidth(-1); - char path[MAX_PATH_LENGTH]; - copyString(path, entry.animation ? entry.animation->getPath().c_str() : ""); - if (m_app.getAssetBrowser().resourceInput("", "anim", Span(path), Animation::TYPE)) { - if (entry.animation) entry.animation->getResourceManager().unload(*entry.animation); - ResourceManagerHub& res_manager = m_app.getEngine().getResourceManager(); - entry.animation = res_manager.load(Path(path)); - } - ImGui::PopItemWidth(); - ImGui::NextColumn(); - ImGui::PopID(); - } - ImGui::Columns(); - if (ImGui::Button("Create##create_animation")) { - Controller::AnimationEntry& entry = m_controller->m_animation_entries.emplace(); - entry.animation = nullptr; - entry.set = 0; - entry.slot = 0; - } - } - - if (ImGui::CollapsingHeader("Nodes")) { - GroupNode* parent = m_current_level; - bool use_root_motion = m_controller->m_flags.isSet(Controller::Flags::USE_ROOT_MOTION); - if (ImGui::Checkbox("Use root motion", &use_root_motion)) { - m_controller->m_flags.set(Controller::Flags::USE_ROOT_MOTION, use_root_motion); - } - for (u32 i = 0; i < (u32)parent->m_children.size(); ++i) { - GroupNode::Child& ch = parent->m_children[i]; - if (ImGui::TreeNodeEx(ch.node, 0, "%s", ch.node->m_name.c_str())) { - if (ImGui::SmallButton("Remove")) { - LUMIX_DELETE(m_controller->m_allocator, parent->m_children[i].node); - parent->m_children.erase(i); - ImGui::TreePop(); - continue; + ImGui::PopItemWidth(); + ImGui::NextColumn(); + ImGui::PushItemWidth(-1); + char path[MAX_PATH_LENGTH]; + copyString(path, entry.animation ? entry.animation->getPath().c_str() : ""); + if (m_app.getAssetBrowser().resourceInput("", "anim", Span(path), Animation::TYPE)) { + if (entry.animation) entry.animation->getResourceManager().unload(*entry.animation); + ResourceManagerHub& res_manager = m_app.getEngine().getResourceManager(); + entry.animation = res_manager.load(Path(path)); } - conditionInput("Condition", m_controller->m_inputs, Ref(ch.condition_str), Ref(ch.condition)); - ui_dispatch(*ch.node, *this); - ImGui::TreePop(); + ImGui::PopItemWidth(); + ImGui::NextColumn(); + ImGui::PopID(); + } + ImGui::Columns(); + if (ImGui::Button("Create##create_animation")) { + Controller::AnimationEntry& entry = m_controller->m_animation_entries.emplace(); + entry.animation = nullptr; + entry.set = 0; + entry.slot = 0; } } - } - if (ImGui::CollapsingHeader("Transitions")) { - Array& children = m_current_level->m_children; - if (children.empty()) { - ImGui::Text("No child nodes."); - } - else { - for (GroupNode::Child& child : children) { - for (GroupNode::Child::Transition& tr : child.transitions) { - const char* name_from = child.node->m_name.c_str(); - const char* name_to = children[tr.to].node->m_name.c_str(); - if (!ImGui::TreeNodeEx(&tr, 0, "%s -> %s", name_from, name_to)) continue; + /*if (ImGui::CollapsingHeader("Transitions")) { + Array& children = m_current_level->m_children; + if (children.empty()) { + ImGui::Text("No child nodes."); + } + else { + for (GroupNode::Child& child : children) { + for (GroupNode::Child::Transition& tr : child.transitions) { + const char* name_from = child.node->m_name.c_str(); + const char* name_to = children[tr.to].node->m_name.c_str(); + if (!ImGui::TreeNodeEx(&tr, 0, "%s -> %s", name_from, name_to)) continue; + + u32 from = u32(&child - children.begin()); + if (nodeInput("From", Ref(from), children)) { + children[from].transitions.push(tr); + child.transitions.erase(u32(&tr - child.transitions.begin())); + ImGui::TreePop(); + break; + } + + nodeInput("To", Ref(tr.to), children); + conditionInput("Condition", m_controller->m_inputs, Ref(tr.condition_str), Ref(tr.condition)); - u32 from = u32(&child - children.begin()); - if (nodeInput("From", Ref(from), children)) { - children[from].transitions.push(tr); - child.transitions.erase(u32(&tr - child.transitions.begin())); ImGui::TreePop(); - break; } + } - nodeInput("To", Ref(tr.to), children); - conditionInput("Condition", m_controller->m_inputs, Ref(tr.condition_str), Ref(tr.condition)); - - ImGui::TreePop(); + if (ImGui::Button("Add")) { + GroupNode::Child::Transition& transition = children[0].transitions.emplace(m_controller->m_allocator); + transition.to = 0; } } + }*/ - if (ImGui::Button("Add")) { - GroupNode::Child::Transition& transition = children[0].transitions.emplace(m_controller->m_allocator); - transition.to = 0; + if (ImGui::CollapsingHeader("Bone masks")) { + char model_path[MAX_PATH_LENGTH]; + copyString(model_path, m_model ? m_model->getPath().c_str() : ""); + if (m_app.getAssetBrowser().resourceInput("Model", "model", Span(model_path), Model::TYPE)) { + m_model = m_app.getEngine().getResourceManager().load(Path(model_path)); } - } - } - if (ImGui::CollapsingHeader("Bone masks")) { - char model_path[MAX_PATH_LENGTH]; - copyString(model_path, m_model ? m_model->getPath().c_str() : ""); - if (m_app.getAssetBrowser().resourceInput("Model", "model", Span(model_path), Model::TYPE)) { - m_model = m_app.getEngine().getResourceManager().load(Path(model_path)); - } - - if (m_model) { - for (BoneMask& mask : m_controller->m_bone_masks) { - if (!ImGui::TreeNodeEx(&mask, 0, "%s", mask.name.data)) continue; + if (m_model) { + for (BoneMask& mask : m_controller->m_bone_masks) { + if (!ImGui::TreeNodeEx(&mask, 0, "%s", mask.name.data)) continue; - ImGui::InputText("Name", mask.name.data, sizeof(mask.name.data)); - for (u32 i = 0, c = m_model->getBoneCount(); i < c; ++i) { - const char* bone_name = m_model->getBone(i).name.c_str(); - const u32 bone_name_hash = crc32(bone_name); - const bool is_masked = mask.bones.find(bone_name_hash).isValid(); - bool b = is_masked; - if (ImGui::Checkbox(bone_name, &b)) { - ASSERT(b != is_masked); - if (is_masked) { - mask.bones.erase(bone_name_hash); - } - else { - mask.bones.insert(bone_name_hash, 1); - } - } - } - ImGui::TreePop(); - } - if (ImGui::Button("Add layer")) m_controller->m_bone_masks.emplace(m_controller->m_allocator); - } - } - - if (ImGui::CollapsingHeader("IK")) { - char model_path[MAX_PATH_LENGTH]; - copyString(model_path, m_model ? m_model->getPath().c_str() : ""); - if (m_app.getAssetBrowser().resourceInput("Model", "model", Span(model_path), Model::TYPE)) { - m_model = m_app.getEngine().getResourceManager().load(Path(model_path)); - } - if(m_model) { - for (u32 i = 0; i < m_controller->m_ik_count; ++i) { - const StaticString<32> label("Chain ", i); - if (ImGui::TreeNode(label)) { - Anim::Controller::IK& ik = m_controller->m_ik[i]; - ASSERT(ik.bones_count > 0); - const u32 bones_count = m_model->getBoneCount(); - auto leaf_iter = m_model->getBoneIndex(ik.bones[ik.bones_count - 1]); - if (ImGui::BeginCombo("Leaf", leaf_iter.isValid() ? m_model->getBone(leaf_iter.value()).name.c_str() : "N/A")) { - for (u32 j = 0; j < bones_count; ++j) { - const char* bone_name = m_model->getBone(j).name.c_str(); - if (ImGui::Selectable(bone_name)) { - ik.bones_count = 1; - ik.bones[0] = crc32(bone_name); + ImGui::InputText("Name", mask.name.data, sizeof(mask.name.data)); + for (u32 i = 0, c = m_model->getBoneCount(); i < c; ++i) { + const char* bone_name = m_model->getBone(i).name.c_str(); + const u32 bone_name_hash = crc32(bone_name); + const bool is_masked = mask.bones.find(bone_name_hash).isValid(); + bool b = is_masked; + if (ImGui::Checkbox(bone_name, &b)) { + ASSERT(b != is_masked); + if (is_masked) { + mask.bones.erase(bone_name_hash); + } + else { + mask.bones.insert(bone_name_hash, 1); } } - ImGui::EndCombo(); - } - for (u32 j = ik.bones_count - 2; j != 0xffFFffFF; --j) { //-V621 - auto iter = m_model->getBoneIndex(ik.bones[j]); - if (iter.isValid()) { - ImGui::Text("%s", m_model->getBone(iter.value()).name.c_str()); - } - else { - ImGui::Text("Unknown bone"); - } } + ImGui::TreePop(); + } + if (ImGui::Button("Add layer")) m_controller->m_bone_masks.emplace(m_controller->m_allocator); + } + } - auto iter = m_model->getBoneIndex(ik.bones[0]); - if (iter.isValid()) { - if (ik.bones_count < lengthOf(ik.bones)) { - const int parent_idx = m_model->getBone(iter.value()).parent_idx; - if (parent_idx >= 0) { - const char* bone_name = m_model->getBone(parent_idx).name.c_str(); - const StaticString<64> add_label("Add ", bone_name); - if (ImGui::Button(add_label)) { - memmove(&ik.bones[1], &ik.bones[0], sizeof(ik.bones[0]) * ik.bones_count); + if (ImGui::CollapsingHeader("IK")) { + char model_path[MAX_PATH_LENGTH]; + copyString(model_path, m_model ? m_model->getPath().c_str() : ""); + if (m_app.getAssetBrowser().resourceInput("Model", "model", Span(model_path), Model::TYPE)) { + m_model = m_app.getEngine().getResourceManager().load(Path(model_path)); + } + if(m_model) { + for (u32 i = 0; i < m_controller->m_ik_count; ++i) { + const StaticString<32> label("Chain ", i); + if (ImGui::TreeNode(label)) { + Controller::IK& ik = m_controller->m_ik[i]; + ASSERT(ik.bones_count > 0); + const u32 bones_count = m_model->getBoneCount(); + auto leaf_iter = m_model->getBoneIndex(ik.bones[ik.bones_count - 1]); + if (ImGui::BeginCombo("Leaf", leaf_iter.isValid() ? m_model->getBone(leaf_iter.value()).name.c_str() : "N/A")) { + for (u32 j = 0; j < bones_count; ++j) { + const char* bone_name = m_model->getBone(j).name.c_str(); + if (ImGui::Selectable(bone_name)) { + ik.bones_count = 1; ik.bones[0] = crc32(bone_name); - ++ik.bones_count; } } + ImGui::EndCombo(); + } + for (u32 j = ik.bones_count - 2; j != 0xffFFffFF; --j) { //-V621 + auto iter = m_model->getBoneIndex(ik.bones[j]); + if (iter.isValid()) { + ImGui::Text("%s", m_model->getBone(iter.value()).name.c_str()); + } + else { + ImGui::Text("Unknown bone"); + } + } + + auto iter = m_model->getBoneIndex(ik.bones[0]); + if (iter.isValid()) { + if (ik.bones_count < lengthOf(ik.bones)) { + const int parent_idx = m_model->getBone(iter.value()).parent_idx; + if (parent_idx >= 0) { + const char* bone_name = m_model->getBone(parent_idx).name.c_str(); + const StaticString<64> add_label("Add ", bone_name); + if (ImGui::Button(add_label)) { + memmove(&ik.bones[1], &ik.bones[0], sizeof(ik.bones[0]) * ik.bones_count); + ik.bones[0] = crc32(bone_name); + ++ik.bones_count; + } + } + } + else { + ImGui::Text("IK is full"); + } } else { - ImGui::Text("IK is full"); + ImGui::Text("Unknown bone."); } - } - else { - ImGui::Text("Unknown bone."); - } - if (ik.bones_count > 1) { - ImGui::SameLine(); - if (ImGui::Button("Pop")) --ik.bones_count; - } + if (ik.bones_count > 1) { + ImGui::SameLine(); + if (ImGui::Button("Pop")) --ik.bones_count; + } - ImGui::TreePop(); + ImGui::TreePop(); + } + } + + if (m_controller->m_ik_count < (u32)lengthOf(m_controller->m_ik[0].bones) && ImGui::Button("Add chain")) { + m_controller->m_ik[m_controller->m_ik_count].bones_count = 1; + m_controller->m_ik[m_controller->m_ik_count].bones[0] = 0; + ++m_controller->m_ik_count; } } - - if (m_controller->m_ik_count < (u32)lengthOf(m_controller->m_ik[0].bones) && ImGui::Button("Add chain")) { - m_controller->m_ik[m_controller->m_ik_count].bones_count = 1; - m_controller->m_ik[m_controller->m_ik_count].bones[0] = 0; - ++m_controller->m_ik_count; + else { + ImGui::Text("Please select a model."); } } - else { - ImGui::Text("Please select a model."); - } } + ImGui::End(); } - ImGui::End(); + + const char* getName() const override { return "Animation Editor"; } + + StudioApp& m_app; + Controller* m_controller; + Node* m_current_node = nullptr; + Model* m_model = nullptr; + +}; // ControllerEditorImpl + +ControllerEditor& ControllerEditor::create(StudioApp& app) { + return *LUMIX_NEW(app.getAllocator(), ControllerEditorImpl)(app); } +void ControllerEditor::destroy(ControllerEditor& editor) { + ControllerEditorImpl& e = (ControllerEditorImpl&)editor; + LUMIX_DELETE(e.m_app.getAllocator(), &editor); +} } // ns Lumix::Anim \ No newline at end of file diff --git a/src/animation/editor/controller_editor.h b/src/animation/editor/controller_editor.h index dc1825c2d..c0385203b 100644 --- a/src/animation/editor/controller_editor.h +++ b/src/animation/editor/controller_editor.h @@ -12,16 +12,10 @@ namespace Anim { struct ControllerEditor : StudioApp::GUIPlugin { - ControllerEditor(StudioApp& app); - ~ControllerEditor(); + static ControllerEditor& create(StudioApp& app); + static void destroy(ControllerEditor& editor); - void onWindowGUI() override; - const char* getName() const override { return "Animation Editor"; } - - StudioApp& m_app; - struct Controller* m_controller; - struct GroupNode* m_current_level; - Model* m_model = nullptr; + virtual ~ControllerEditor() {} }; diff --git a/src/animation/editor/plugins.cpp b/src/animation/editor/plugins.cpp index 05f8e75d0..6ea4f6b47 100644 --- a/src/animation/editor/plugins.cpp +++ b/src/animation/editor/plugins.cpp @@ -373,7 +373,7 @@ struct StudioAppPlugin : StudioApp::IPlugin { explicit StudioAppPlugin(StudioApp& app) : m_app(app) - , m_anim_editor(app) + , m_anim_editor(nullptr) { } @@ -404,7 +404,8 @@ struct StudioAppPlugin : StudioApp::IPlugin m_animable_plugin = LUMIX_NEW(allocator, AnimablePropertyGridPlugin)(m_app); m_app.getPropertyGrid().addPlugin(*m_animable_plugin); - m_app.addPlugin(m_anim_editor); + m_anim_editor = &Anim::ControllerEditor::create(m_app); + m_app.addPlugin(*m_anim_editor); } bool showGizmo(UniverseView&, ComponentUID) override { return false; } @@ -426,7 +427,8 @@ struct StudioAppPlugin : StudioApp::IPlugin m_app.getPropertyGrid().removePlugin(*m_animable_plugin); LUMIX_DELETE(allocator, m_animable_plugin); - m_app.removePlugin(m_anim_editor); + m_app.removePlugin(*m_anim_editor); + Anim::ControllerEditor::destroy(*m_anim_editor); } @@ -435,7 +437,7 @@ struct StudioAppPlugin : StudioApp::IPlugin AnimationAssetBrowserPlugin* m_animtion_plugin; PropertyAnimationAssetBrowserPlugin* m_prop_anim_plugin; AnimControllerAssetBrowserPlugin* m_anim_ctrl_plugin; - Lumix::Anim::ControllerEditor m_anim_editor; + Anim::ControllerEditor* m_anim_editor; }; diff --git a/src/animation/nodes.cpp b/src/animation/nodes.cpp index 4aa66a1b6..98605ac40 100644 --- a/src/animation/nodes.cpp +++ b/src/animation/nodes.cpp @@ -21,7 +21,7 @@ static LUMIX_FORCE_INLINE LocalRigidTransform getRootMotion(const Animation* ani root_motion.pos = Vec3::ZERO; } if (rotation_idx >= 0) { - root_motion.rot = anim->getRotation(time, translation_idx); + root_motion.rot = anim->getRotation(time, rotation_idx); } else { root_motion.rot = Quat::IDENTITY; diff --git a/src/editor/profiler_ui.cpp b/src/editor/profiler_ui.cpp index 4b63c62e3..a2e4fee33 100644 --- a/src/editor/profiler_ui.cpp +++ b/src/editor/profiler_ui.cpp @@ -962,7 +962,7 @@ void ProfilerUIImpl::onGUICPUProfiler() } if (ImGui::IsMouseHoveringRect(ImVec2(from_x, from_y), ImVec2(to_x, ImGui::GetCursorScreenPos().y))) { - if (ImGui::IsMouseDragging()) { + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { m_end -= i64((ImGui::GetIO().MouseDelta.x / (to_x - from_x)) * m_range); } const u64 cursor = u64(((ImGui::GetMousePos().x - from_x) / (to_x - from_x)) * m_range) + view_start; diff --git a/src/editor/settings.cpp b/src/editor/settings.cpp index 0a148633f..1d472a11f 100644 --- a/src/editor/settings.cpp +++ b/src/editor/settings.cpp @@ -403,7 +403,7 @@ void Settings::showToolbarSettings() const dragged = nullptr; break; } - if (ImGui::IsItemActive() && ImGui::IsMouseDragging()) + if (ImGui::IsItemActive() && ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { dragged = action; actions.eraseItem(action); diff --git a/src/gui/editor/plugins.cpp b/src/gui/editor/plugins.cpp index d406f1174..7bd32139e 100644 --- a/src/gui/editor/plugins.cpp +++ b/src/gui/editor/plugins.cpp @@ -140,7 +140,7 @@ struct SpritePlugin final : AssetBrowser::IPlugin if (ImGui::IsItemActive()) { static int start_drag_value; - if (ImGui::IsMouseDragging()) + if (ImGui::IsMouseDragging(ImGuiMouseButton_Left)) { ImVec2 drag = ImGui::GetMouseDragDelta(); if (vertical) diff --git a/src/renderer/gpu/gpu.cpp b/src/renderer/gpu/gpu.cpp index 4a51e3498..774361cfd 100644 --- a/src/renderer/gpu/gpu.cpp +++ b/src/renderer/gpu/gpu.cpp @@ -1282,7 +1282,7 @@ void swapBuffers() #ifdef _WIN32 for (WindowContext& ctx : g_gpu.contexts) { if (!ctx.window_handle) continue; - if (g_gpu.frame == ctx.last_frame) { + if (g_gpu.frame == ctx.last_frame || &ctx == g_gpu.contexts) { SwapBuffers(ctx.device_context); } else {