diff --git a/Editor/AnimationWindow.cpp b/Editor/AnimationWindow.cpp index e9c7d1bc6f..e81e11a537 100644 --- a/Editor/AnimationWindow.cpp +++ b/Editor/AnimationWindow.cpp @@ -927,11 +927,68 @@ void AnimationWindow::Create(EditorComponent* _editor) }); AddWidget(&retargetCombo); + // Root Motion Tick + rootMotionCheckBox.Create("RootMotion: "); + rootMotionCheckBox.SetTooltip("Toggle root bone animation."); + rootMotionCheckBox.SetSize(XMFLOAT2(hei, hei)); + //rootMotionCheckBox.SetPos(XMFLOAT2(x, y += step)); + rootMotionCheckBox.OnClick([&](wi::gui::EventArgs args) { + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); + if (animation != nullptr) + { + if (args.bValue) { + animation->RootMotionOn(); + } + else { + animation->RootMotionOff(); + } + } + }); + rootMotionCheckBox.SetCheckText(ICON_CHECK); + AddWidget(&rootMotionCheckBox); + // Root Bone selector + rootBoneComboBox.Create("Root Bone: "); + rootBoneComboBox.SetSize(XMFLOAT2(wid, hei)); + rootBoneComboBox.SetPos(XMFLOAT2(x, y)); + rootBoneComboBox.SetEnabled(false); + rootBoneComboBox.OnSelect([&](wi::gui::EventArgs args) { + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); + if (animation != nullptr) + { + Entity ent = (Entity)args.userdata; + if (ent != wi::ecs::INVALID_ENTITY) + { + animation->rootMotionBone = ent; + } + else + { + animation->rootMotionBone = wi::ecs::INVALID_ENTITY; + } + } + }); + rootBoneComboBox.SetTooltip("Choose the root bone to evaluate root motion from."); + AddWidget(&rootBoneComboBox); + SetMinimized(true); SetVisible(false); } +// Example function to check if an entity already exists in the list +static bool EntityExistsInList(Entity entity, std::vector entityList) +{ + // Iterate through the list of entities + for (Entity& e : entityList) + { + if (e == entity) + { + // Entity found in the list + return true; + } + } + // Entity not found in the list + return false; +} void AnimationWindow::SetEntity(Entity entity) { @@ -944,6 +1001,32 @@ void AnimationWindow::SetEntity(Entity entity) { this->entity = entity; RefreshKeyframesList(); + + rootBoneComboBox.ClearItems(); + rootBoneComboBox.AddItem("NO ROOT BONE " ICON_DISABLED, wi::ecs::INVALID_ENTITY); + if (animation != nullptr) + { + // Define a list of entities + std::vector bone_list; + // Add items to root bone name combo box. + for (const AnimationComponent::AnimationChannel& channel : animation->channels) + { + if (channel.path == AnimationComponent::AnimationChannel::Path::TRANSLATION || + channel.path == AnimationComponent::AnimationChannel::Path::ROTATION) { + + if (!EntityExistsInList(channel.target, bone_list)) + { + bone_list.push_back(channel.target); + const NameComponent* name = scene.names.GetComponent(channel.target); + if (name != nullptr) + { + rootBoneComboBox.AddItem(name->name, channel.target); + } + } + } + } + bone_list.clear(); + } } } } @@ -986,6 +1069,9 @@ void AnimationWindow::Update() loopedCheckBox.SetCheck(animation.IsLooped()); + rootMotionCheckBox.SetCheck(animation.IsRootMotion()); + rootBoneComboBox.SetSelectedByUserdataWithoutCallback(animation.rootMotionBone); + timerSlider.SetRange(animation.start, animation.end); timerSlider.SetValue(animation.timer); amountSlider.SetValue(animation.amount); @@ -1213,5 +1299,7 @@ void AnimationWindow::ResizeLayout() add(endInput); add(recordCombo); add(retargetCombo); + add(rootMotionCheckBox); + add(rootBoneComboBox); add_fullwidth(keyframesList); } diff --git a/Editor/AnimationWindow.h b/Editor/AnimationWindow.h index c30179caf2..d662a68925 100644 --- a/Editor/AnimationWindow.h +++ b/Editor/AnimationWindow.h @@ -26,6 +26,9 @@ class AnimationWindow : public wi::gui::Window wi::gui::ComboBox retargetCombo; + wi::gui::CheckBox rootMotionCheckBox; + wi::gui::ComboBox rootBoneComboBox; + void Update(); void RefreshKeyframesList(); diff --git a/WickedEngine/wiMath_BindLua.cpp b/WickedEngine/wiMath_BindLua.cpp index 51b81a7c5a..4b070f7715 100644 --- a/WickedEngine/wiMath_BindLua.cpp +++ b/WickedEngine/wiMath_BindLua.cpp @@ -622,6 +622,10 @@ namespace wi::lua lunamethod(Matrix_BindLua, Multiply), lunamethod(Matrix_BindLua, Transpose), lunamethod(Matrix_BindLua, Inverse), + + lunamethod(Matrix_BindLua, GetForward), + lunamethod(Matrix_BindLua, GetUp), + lunamethod(Matrix_BindLua, GetRight), { NULL, NULL } }; Luna::PropertyType Matrix_BindLua::properties[] = { @@ -910,7 +914,72 @@ namespace wi::lua wi::lua::SError(L, "Inverse(Matrix m) not enough arguments!"); return 0; } - + static XMVECTOR GetForward(XMMATRIX* _m) + { + XMVECTOR V = XMVectorSet(XMVectorGetX(_m->r[2]), XMVectorGetY(_m->r[2]), XMVectorGetZ(_m->r[2]), 0.0f); + return V; + } + int Matrix_BindLua::GetForward(lua_State* L) + { + int argc = wi::lua::SGetArgCount(L); + if (argc > 0) + { + Matrix_BindLua* m1 = Luna::lightcheck(L, 1); + if (m1) + { + XMMATRIX matrix = XMLoadFloat4x4(&m1->data); + XMVECTOR V = XMVectorSet(XMVectorGetX(matrix.r[2]), XMVectorGetY(matrix.r[2]), XMVectorGetZ(matrix.r[2]), 0.0f); + Luna::push(L, V); + return 1; + } + } + wi::lua::SError(L, "GetForward(Matrix m) not enough arguments!"); + return 0; + } + static XMVECTOR GetUp(XMMATRIX* _m) + { + XMVECTOR V = XMVectorSet(XMVectorGetX(_m->r[1]), XMVectorGetY(_m->r[1]), XMVectorGetZ(_m->r[1]), 0.0f); + return V; + } + int Matrix_BindLua::GetUp(lua_State* L) + { + int argc = wi::lua::SGetArgCount(L); + if (argc > 0) + { + Matrix_BindLua* m1 = Luna::lightcheck(L, 1); + if (m1) + { + XMMATRIX matrix = XMLoadFloat4x4(&m1->data); + XMVECTOR V = XMVectorSet(XMVectorGetX(matrix.r[1]), XMVectorGetY(matrix.r[1]), XMVectorGetZ(matrix.r[1]), 0.0f); + Luna::push(L, V); + return 1; + } + } + wi::lua::SError(L, "GetUp(Matrix m) not enough arguments!"); + return 0; + } + static XMVECTOR GetRight(XMMATRIX* _m) + { + XMVECTOR V = XMVectorSet(XMVectorGetX(_m->r[0]), XMVectorGetY(_m->r[0]), XMVectorGetZ(_m->r[0]), 0.0f); + return V; + } + int Matrix_BindLua::GetRight(lua_State* L) + { + int argc = wi::lua::SGetArgCount(L); + if (argc > 0) + { + Matrix_BindLua* m1 = Luna::lightcheck(L, 1); + if (m1) + { + XMMATRIX matrix = XMLoadFloat4x4(&m1->data); + XMVECTOR V = XMVectorSet(XMVectorGetX(matrix.r[0]), XMVectorGetY(matrix.r[0]), XMVectorGetZ(matrix.r[0]), 0.0f); + Luna::push(L, V); + return 1; + } + } + wi::lua::SError(L, "GetRight(Matrix m) not enough arguments!"); + return 0; + } void Matrix_BindLua::Bind() { diff --git a/WickedEngine/wiMath_BindLua.h b/WickedEngine/wiMath_BindLua.h index 9538ba68f3..3384bcb0f9 100644 --- a/WickedEngine/wiMath_BindLua.h +++ b/WickedEngine/wiMath_BindLua.h @@ -109,6 +109,10 @@ namespace wi::lua int Transpose(lua_State* L); int Inverse(lua_State* L); + int GetForward(lua_State* L); + int GetUp(lua_State* L); + int GetRight(lua_State* L); + static void Bind(); }; struct MatrixProperty diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index afa0aa44f0..dd2e3135ee 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -2090,6 +2090,9 @@ namespace wi::scene // The interpolated raw values will be blended on top of component values: const float t = animation.amount; + // CheckIf this channel is the root motion bone or not. + const bool isRootBone = (animation.IsRootMotion() && animation.rootMotionBone != wi::ecs::INVALID_ENTITY && (target_transform == transforms.GetComponent(animation.rootMotionBone))); + if (target_transform != nullptr) { target_transform->SetDirty(); @@ -2117,7 +2120,40 @@ namespace wi::scene } } const XMVECTOR T = XMVectorLerp(aT, bT, t); - XMStoreFloat3(&target_transform->translation_local, T); + if (!isRootBone) + { + // Not root motion bone. + XMStoreFloat3(&target_transform->translation_local, T); + } + else + { + if (XMVector4Equal(animation.rootPrevTranslation, animation.INVALID_VECTOR) || animation.end < animation.prevLocTimer) + { + // If root motion bone. + animation.rootPrevTranslation = T; + } + + XMVECTOR rotation_quat = animation.rootPrevRotation; + + if (XMVector4Equal(animation.rootPrevRotation, animation.INVALID_VECTOR) || animation.end < animation.prevRotTimer) + { + // If root motion bone. + rotation_quat = XMLoadFloat4(&target_transform->rotation_local); + } + + const XMVECTOR root_trans = XMVectorSubtract(T, animation.rootPrevTranslation); + XMVECTOR inverseQuaternion = XMQuaternionInverse(rotation_quat); + XMVECTOR rotatedDirectionVector = XMVector3Rotate(root_trans, inverseQuaternion); + + XMMATRIX mat = XMLoadFloat4x4(&target_transform->world); + rotatedDirectionVector = XMVector4Transform(rotatedDirectionVector, mat); + + // Store root motion offset + XMStoreFloat3(&animation.rootTranslationOffset, rotatedDirectionVector); + // If root motion bone. + animation.rootPrevTranslation = T; + animation.prevLocTimer = animation.timer; + } } break; case AnimationComponent::AnimationChannel::Path::ROTATION: @@ -2141,7 +2177,41 @@ namespace wi::scene } } const XMVECTOR R = XMQuaternionSlerp(aR, bR, t); - XMStoreFloat4(&target_transform->rotation_local, R); + if (!isRootBone) + { + // Not root motion bone. + XMStoreFloat4(&target_transform->rotation_local, R); + } + else + { + if (XMVector4Equal(animation.rootPrevRotation, animation.INVALID_VECTOR) || animation.end < animation.prevRotTimer) + { + // If root motion bone. + animation.rootPrevRotation = R; + } + + // Assuming q1 and q2 are the two quaternions you want to subtract + // // Let's say you want to find the relative rotation from q1 to q2 + XMMATRIX mat1 = XMMatrixRotationQuaternion(animation.rootPrevRotation); + XMMATRIX mat2 = XMMatrixRotationQuaternion(R); + // Compute the relative rotation matrix by multiplying the inverse of the first rotation + // by the second rotation + XMMATRIX relativeRotationMatrix = XMMatrixMultiply(XMMatrixTranspose(mat1), mat2); + // Extract the quaternion representing the relative rotation + XMVECTOR relativeRotationQuaternion = XMQuaternionRotationMatrix(relativeRotationMatrix); + + // Store root motion offset + XMStoreFloat4(&animation.rootRotationOffset, relativeRotationQuaternion); + // Swap Y and Z Axis for Unknown reason + const float Y = animation.rootRotationOffset.y; + animation.rootRotationOffset.y = animation.rootRotationOffset.z; + animation.rootRotationOffset.z = Y; + + // If root motion bone. + animation.rootPrevRotation = R; + animation.prevRotTimer = animation.timer; + } + } break; case AnimationComponent::AnimationChannel::Path::SCALE: diff --git a/WickedEngine/wiScene.h b/WickedEngine/wiScene.h index 32bfa2e81b..dbdb2b78f7 100644 --- a/WickedEngine/wiScene.h +++ b/WickedEngine/wiScene.h @@ -42,7 +42,7 @@ namespace wi::scene wi::ecs::ComponentManager& probes = componentLibrary.Register("wi::scene::Scene::probes", 1); // version = 1 wi::ecs::ComponentManager& forces = componentLibrary.Register("wi::scene::Scene::forces", 1); // version = 1 wi::ecs::ComponentManager& decals = componentLibrary.Register("wi::scene::Scene::decals", 1); // version = 1 - wi::ecs::ComponentManager& animations = componentLibrary.Register("wi::scene::Scene::animations", 1); // version = 1 + wi::ecs::ComponentManager& animations = componentLibrary.Register("wi::scene::Scene::animations", 2); // version = 2 wi::ecs::ComponentManager& animation_datas = componentLibrary.Register("wi::scene::Scene::animation_datas"); wi::ecs::ComponentManager& emitters = componentLibrary.Register("wi::scene::Scene::emitters"); wi::ecs::ComponentManager& hairs = componentLibrary.Register("wi::scene::Scene::hairs"); diff --git a/WickedEngine/wiScene_BindLua.cpp b/WickedEngine/wiScene_BindLua.cpp index 29e9687ae2..1aac29af50 100644 --- a/WickedEngine/wiScene_BindLua.cpp +++ b/WickedEngine/wiScene_BindLua.cpp @@ -3685,6 +3685,11 @@ Luna::FunctionType AnimationComponent_BindLua::metho lunamethod(AnimationComponent_BindLua, SetStart), lunamethod(AnimationComponent_BindLua, GetEnd), lunamethod(AnimationComponent_BindLua, SetEnd), + lunamethod(AnimationComponent_BindLua, IsRootMotion), + lunamethod(AnimationComponent_BindLua, RootMotionOn), + lunamethod(AnimationComponent_BindLua, RootMotionOff), + lunamethod(AnimationComponent_BindLua, GetRootTranslation), + lunamethod(AnimationComponent_BindLua, GetRootRotation), { NULL, NULL } }; Luna::PropertyType AnimationComponent_BindLua::properties[] = { @@ -3814,6 +3819,36 @@ int AnimationComponent_BindLua::SetEnd(lua_State* L) return 0; } +int AnimationComponent_BindLua::IsRootMotion(lua_State* L) +{ + wi::lua::SSetBool(L, component->IsRootMotion()); + return 1; +} + +int AnimationComponent_BindLua::RootMotionOn(lua_State* L) +{ + component->RootMotionOn(); + return 0; +} + +int AnimationComponent_BindLua::RootMotionOff(lua_State* L) +{ + component->RootMotionOff(); + return 0; +} + +int AnimationComponent_BindLua::GetRootTranslation(lua_State* L) +{ + Luna::push(L, component->rootTranslationOffset); + return 1; +} + +int AnimationComponent_BindLua::GetRootRotation(lua_State* L) +{ + Luna::push(L, component->rootRotationOffset); + return 1; +} + diff --git a/WickedEngine/wiScene_BindLua.h b/WickedEngine/wiScene_BindLua.h index ed377cc86b..92cec6c666 100644 --- a/WickedEngine/wiScene_BindLua.h +++ b/WickedEngine/wiScene_BindLua.h @@ -349,6 +349,13 @@ namespace wi::lua::scene int SetStart(lua_State* L); int GetEnd(lua_State* L); int SetEnd(lua_State* L); + + // For Rootmotion + int IsRootMotion(lua_State* L); + int RootMotionOn(lua_State* L); + int RootMotionOff(lua_State* L); + int GetRootTranslation(lua_State* L); + int GetRootRotation(lua_State* L); }; class MaterialComponent_BindLua diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index 6ed317e38f..fc714dc0b8 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -1290,6 +1290,7 @@ namespace wi::scene EMPTY = 0, PLAYING = 1 << 0, LOOPED = 1 << 1, + ROOT_MOTION = 1 << 2 }; uint32_t _flags = LOOPED; float start = 0; @@ -1414,16 +1415,36 @@ namespace wi::scene wi::vector morph_weights_temp; float last_update_time = 0; + // Root Motion + XMFLOAT3 rootTranslationOffset; + XMFLOAT4 rootRotationOffset; + wi::ecs::Entity rootMotionBone; + + XMVECTOR rootPrevTranslation = XMVectorSet(-69, 420, 69, -420); + XMVECTOR rootPrevRotation = XMVectorSet(-69, 420, 69, -420); + XMVECTOR INVALID_VECTOR = XMVectorSet(-69, 420, 69, -420); + float prevLocTimer; + float prevRotTimer; + // Root Motion + inline bool IsPlaying() const { return _flags & PLAYING; } inline bool IsLooped() const { return _flags & LOOPED; } inline float GetLength() const { return end - start; } inline bool IsEnded() const { return timer >= end; } + inline bool IsRootMotion() const { return _flags & ROOT_MOTION; } inline void Play() { _flags |= PLAYING; } inline void Pause() { _flags &= ~PLAYING; } inline void Stop() { Pause(); timer = 0.0f; last_update_time = timer; } inline void SetLooped(bool value = true) { if (value) { _flags |= LOOPED; } else { _flags &= ~LOOPED; } } + inline void RootMotionOn() { _flags |= ROOT_MOTION; } + inline void RootMotionOff() { _flags &= ~ROOT_MOTION; } + inline XMFLOAT3 GetRootTranslation() const { return rootTranslationOffset; } + inline XMFLOAT4 GetRootRotation() const { return rootRotationOffset; } + inline wi::ecs::Entity GetRootMotionBone() const { return rootMotionBone; } + inline void SetRootMotionBone(wi::ecs::Entity _rootMotionBone) { rootMotionBone = _rootMotionBone; } + void Serialize(wi::Archive& archive, wi::ecs::EntitySerializer& seri); }; diff --git a/WickedEngine/wiScene_Serializers.cpp b/WickedEngine/wiScene_Serializers.cpp index b85774f887..854e7c3a4b 100644 --- a/WickedEngine/wiScene_Serializers.cpp +++ b/WickedEngine/wiScene_Serializers.cpp @@ -1163,6 +1163,13 @@ namespace wi::scene } } } + + + if (seri.GetVersion() >= 2) + { + // Root Bone Name + SerializeEntity(archive, rootMotionBone, seri); + } } void AnimationDataComponent::Serialize(wi::Archive& archive, EntitySerializer& seri) {