diff --git a/Content/Documentation/ScriptingAPI-Documentation.md b/Content/Documentation/ScriptingAPI-Documentation.md index 7b8d8a8de..b20565dd9 100644 --- a/Content/Documentation/ScriptingAPI-Documentation.md +++ b/Content/Documentation/ScriptingAPI-Documentation.md @@ -946,8 +946,8 @@ Describes an orientation in 3D space. - Play() - Stop() - Pause() -- SetLooped(bool value) -- IsLooped() : bool result +- SetLooped(bool value) -- Sets the animation to repeat continuously. +- IsLooped() : bool -- Returns true if the animation is set to repeat continuously. - IsPlaying() : bool result - SetTimer(float value) - GetTimer() : float result @@ -957,6 +957,12 @@ Describes an orientation in 3D space. - SetStart(float value) - GetEnd() : float result - SetEnd(float value) +- SetPingPong(bool value) -- Sets the animation to play forward and then backwards repeatedly. +- IsPingPong() : bool -- Returns true if the animation is set to play forward and then backwards repeatedly. +- SetPlayOnce() -- Sets the animation to play once. +- IsPlayingOnce() : bool -- Returns true if the animation is set to play once. + + #### MaterialComponent - _flags : int diff --git a/Editor/AnimationWindow.cpp b/Editor/AnimationWindow.cpp index d9a3e3d5e..7b5abce1a 100644 --- a/Editor/AnimationWindow.cpp +++ b/Editor/AnimationWindow.cpp @@ -12,7 +12,6 @@ void AnimationWindow::Create(EditorComponent* _editor) closeButton.SetTooltip("Delete AnimationComponent"); OnClose([=](wi::gui::EventArgs args) { - wi::Archive& archive = editor->AdvanceHistory(); archive << EditorComponent::HISTORYOP_COMPONENT_DATA; editor->RecordEntity(archive, entity); @@ -29,6 +28,8 @@ void AnimationWindow::Create(EditorComponent* _editor) float hei = 18; float wid = 200; float step = hei + 4; + float padding = 4; + float lastDirection = 1; infoLabel.Create(""); infoLabel.SetSize(XMFLOAT2(100, 50)); @@ -71,24 +72,72 @@ void AnimationWindow::Create(EditorComponent* _editor) modeComboBox.SetTooltip("Choose how animation data is interpreted between keyframes.\nNote that Cubic spline sampling requires spline animation data, otherwise, it will fall back to Linear!\nCurrently spline animation data creation is not supported, but it can be imported from GLTF/VRM models."); AddWidget(&modeComboBox); - loopedCheckBox.Create("Looped: "); - loopedCheckBox.SetTooltip("Toggle animation looping behaviour."); - loopedCheckBox.SetSize(XMFLOAT2(hei, hei)); - loopedCheckBox.SetPos(XMFLOAT2(x, y += step)); - loopedCheckBox.OnClick([&](wi::gui::EventArgs args) { + loopTypeButton.Create("LoopType"); + loopTypeButton.SetText(ICON_LOOP); + loopTypeButton.SetDescription("Loop type: "); + loopTypeButton.SetSize(XMFLOAT2(hei, hei)); + loopTypeButton.SetPos(XMFLOAT2(x, y += step)); + loopTypeButton.OnClick([&](wi::gui::EventArgs args) { AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); if (animation != nullptr) { - animation->SetLooped(args.bValue); + if (animation->IsLooped()) + { + animation->SetPingPong(); + } + else if (animation->IsPingPong()) + { + animation->SetPlayOnce(); + } + else + { + animation->SetLooped(); + } } }); - loopedCheckBox.SetCheckText(ICON_LOOP); - AddWidget(&loopedCheckBox); + AddWidget(&loopTypeButton); - playButton.Create(ICON_PLAY); - playButton.SetSize(XMFLOAT2(100, hei)); - playButton.SetPos(XMFLOAT2(loopedCheckBox.GetPos().x + loopedCheckBox.GetSize().x + 5, y)); - playButton.OnClick([&](wi::gui::EventArgs args) { + backwardsButton.Create("Backwards"); + backwardsButton.SetText(ICON_PLAY); + backwardsButton.font.params.enableFlipHorizontally(); + backwardsButton.SetTooltip("Play the animation backwards from the current position."); + backwardsButton.SetSize(XMFLOAT2(30, hei)); + backwardsButton.SetPos(XMFLOAT2(loopTypeButton.GetPos().x + loopTypeButton.GetSize().x + padding, y)); + backwardsButton.OnClick([&](wi::gui::EventArgs args) { + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); + if (animation != nullptr) + { + if (animation->timer > animation->start) // Only play backwards if it is not in the start position. + { + animation->speed = -speedSlider.GetValue(); + animation->Play(); + } + } + }); + AddWidget(&backwardsButton); + + backwardsFromEndButton.Create("BackwardsFromEnd"); + backwardsFromEndButton.SetText(ICON_BACKWARD); + backwardsFromEndButton.SetTooltip("Play the animation backwards starting from the end."); + backwardsFromEndButton.SetSize(backwardsButton.GetSize()); + backwardsFromEndButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x + padding, y)); + backwardsFromEndButton.OnClick([&](wi::gui::EventArgs args) { + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); + if (animation != nullptr) + { + animation->timer = animation->end; + animation->speed = -speedSlider.GetValue(); + animation->Play(); + } + }); + AddWidget(&backwardsFromEndButton); + + stopButton.Create("Stop"); + stopButton.SetText(ICON_STOP); + stopButton.SetTooltip("Stop."); + stopButton.SetSize(backwardsButton.GetSize()); + stopButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 2 + padding * 2, y)); + stopButton.OnClick([&](wi::gui::EventArgs args) { AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); if (animation != nullptr) { @@ -98,25 +147,47 @@ void AnimationWindow::Create(EditorComponent* _editor) } else { + animation->Stop(); + } + } + }); + AddWidget(&stopButton); + + playFromStartButton.Create("PlayFromStart"); + playFromStartButton.SetText(ICON_FORWARD); + playFromStartButton.SetTooltip("Play the animation from the beginning."); + playFromStartButton.SetSize(backwardsButton.GetSize()); + playFromStartButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 3 + padding * 3, y)); + playFromStartButton.OnClick([&](wi::gui::EventArgs args) { + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); + if (animation != nullptr) + { + animation->timer = animation->start; + animation->speed = speedSlider.GetValue(); + animation->Play(); + + } + }); + AddWidget(&playFromStartButton); + + playButton.Create("Play"); + playButton.SetText(ICON_PLAY); + playButton.SetTooltip("Play the animation from the current position."); + playButton.SetSize(backwardsButton.GetSize()); + playButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 4 + padding * 4, y)); + playButton.OnClick([&](wi::gui::EventArgs args) { + AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); + if (animation != nullptr) + { + if (animation->timer < animation->end) // Only play forward if it is not in the end position. + { + animation->speed = speedSlider.GetValue(); animation->Play(); } } }); AddWidget(&playButton); - stopButton.Create(ICON_STOP); - stopButton.SetTooltip("Stop"); - stopButton.SetSize(XMFLOAT2(70, hei)); - stopButton.SetPos(XMFLOAT2(playButton.GetPos().x + playButton.GetSize().x + 5, y)); - stopButton.OnClick([&](wi::gui::EventArgs args) { - AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); - if (animation != nullptr) - { - animation->Stop(); - } - }); - AddWidget(&stopButton); - timerSlider.Create(0, 1, 0, 100000, "Timer: "); timerSlider.SetSize(XMFLOAT2(wid, hei)); timerSlider.SetPos(XMFLOAT2(x, y += step)); @@ -152,7 +223,9 @@ void AnimationWindow::Create(EditorComponent* _editor) AnimationComponent* animation = editor->GetCurrentScene().animations.GetComponent(entity); if (animation != nullptr) { - animation->speed = args.fValue; + // Adjusts the new animation speed based on the previous direction of animation->speed. + lastDirection = std::signbit(animation->speed) ? -1 : 1; + animation->speed = args.fValue * lastDirection; } }); speedSlider.SetEnabled(false); @@ -969,7 +1042,6 @@ void AnimationWindow::Create(EditorComponent* _editor) rootBoneComboBox.SetTooltip("Choose the root bone to evaluate root motion from."); AddWidget(&rootBoneComboBox); - SetMinimized(true); SetVisible(false); @@ -1049,13 +1121,29 @@ void AnimationWindow::Update() if (animation.IsPlaying()) { - playButton.SetText(ICON_PAUSE); - playButton.SetTooltip("Pause"); + stopButton.SetText(ICON_PAUSE); + stopButton.SetTooltip("Pause"); } else { - playButton.SetText(ICON_PLAY); - playButton.SetTooltip("Play"); + stopButton.SetText(ICON_STOP); + stopButton.SetTooltip("Stop"); + } + + if (animation.IsLooped()) + { + loopTypeButton.SetText(ICON_LOOP); + loopTypeButton.SetTooltip("Looping is enabled. The animation will repeat continuously."); + } + else if (animation.IsPingPong()) + { + loopTypeButton.SetText(ICON_PINGPONG); + loopTypeButton.SetTooltip("Ping-Pong is enabled. The animation will play forward and then backwards repeatedly."); + } + else + { + loopTypeButton.SetText("-"); + loopTypeButton.SetTooltip("No loop. The animation will play once."); } if(!animation.samplers.empty()) @@ -1067,15 +1155,12 @@ void AnimationWindow::Update() modeComboBox.SetSelectedByUserdataWithoutCallback(AnimationComponent::AnimationSampler().mode); } - 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); - speedSlider.SetValue(animation.speed); startInput.SetValue(animation.start); endInput.SetValue(animation.end); } @@ -1281,14 +1366,20 @@ void AnimationWindow::ResizeLayout() add_fullwidth(infoLabel); add(modeComboBox); - loopedCheckBox.SetPos(XMFLOAT2(margin_left, y)); - const float l = loopedCheckBox.GetPos().x + loopedCheckBox.GetSize().x + padding; - const float r = width - margin_right - padding; + loopTypeButton.SetPos(XMFLOAT2(margin_left, y)); + const float l = loopTypeButton.GetPos().x + loopTypeButton.GetSize().x + padding; + const float r = width - margin_right - padding * 4; const float diff = r - l; - playButton.SetSize(XMFLOAT2(diff * 0.5f, playButton.GetSize().y)); - stopButton.SetSize(playButton.GetSize()); - playButton.SetPos(XMFLOAT2(loopedCheckBox.GetPos().x + loopedCheckBox.GetSize().x + padding, y)); - stopButton.SetPos(XMFLOAT2(playButton.GetPos().x + playButton.GetSize().x + padding, y)); + backwardsButton.SetSize(XMFLOAT2(diff/5, backwardsButton.GetSize().y)); + backwardsFromEndButton.SetSize(backwardsButton.GetSize()); + stopButton.SetSize(backwardsButton.GetSize()); + playFromStartButton.SetSize(backwardsButton.GetSize()); + playButton.SetSize(backwardsButton.GetSize()); + backwardsButton.SetPos(XMFLOAT2(loopTypeButton.GetPos().x + loopTypeButton.GetSize().x + padding, y)); + backwardsFromEndButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x + padding, y)); + stopButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 2 + padding * 2, y)); + playFromStartButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 3 + padding * 3, y)); + playButton.SetPos(XMFLOAT2(backwardsButton.GetPos().x + backwardsButton.GetSize().x * 4 + padding * 4, y)); y += stopButton.GetSize().y; y += padding; diff --git a/Editor/AnimationWindow.h b/Editor/AnimationWindow.h index d662a6892..a4babe79c 100644 --- a/Editor/AnimationWindow.h +++ b/Editor/AnimationWindow.h @@ -12,8 +12,11 @@ public: wi::gui::Label infoLabel; wi::gui::ComboBox modeComboBox; - wi::gui::CheckBox loopedCheckBox; + wi::gui::Button loopTypeButton; wi::gui::Button playButton; + wi::gui::Button playFromStartButton; + wi::gui::Button backwardsButton; + wi::gui::Button backwardsFromEndButton; wi::gui::Button stopButton; wi::gui::Slider timerSlider; wi::gui::Slider amountSlider; @@ -35,4 +38,3 @@ public: void ResizeLayout() override; }; - diff --git a/Editor/IconDefinitions.h b/Editor/IconDefinitions.h index fb015848b..e0218c632 100644 --- a/Editor/IconDefinitions.h +++ b/Editor/IconDefinitions.h @@ -69,7 +69,10 @@ #define ICON_SQUARE ICON_FA_SQUARE_FULL #define ICON_CUBE ICON_FA_CUBE #define ICON_LOOP ICON_FA_REPEAT +#define ICON_PINGPONG ICON_FA_ARROWS_LEFT_RIGHT_TO_LINE #define ICON_PLAY ICON_FA_PLAY +#define ICON_BACKWARD ICON_FA_BACKWARD +#define ICON_FORWARD ICON_FA_FORWARD #define ICON_PAUSE ICON_FA_PAUSE #define ICON_STOP ICON_FA_STOP #define ICON_PEN ICON_FA_PEN diff --git a/WickedEngine/wiScene.cpp b/WickedEngine/wiScene.cpp index 915373cb9..b2b453665 100644 --- a/WickedEngine/wiScene.cpp +++ b/WickedEngine/wiScene.cpp @@ -2681,12 +2681,46 @@ namespace wi::scene } - if (animation.IsLooped() && animation.timer > animation.end) + if (animation.timer > animation.end && animation.speed > 0) { - animation.timer = animation.start; - for (auto& channel : animation.channels) + if (animation.IsLooped()) { - channel.next_event = 0; + animation.timer = animation.start; + for (auto& channel : animation.channels) + { + channel.next_event = 0; + } + } + else if (animation.IsPingPong()) + { + animation.timer = animation.end; + animation.speed = -animation.speed; + } + else + { + animation.timer = animation.end; + animation.Pause(); + } + } + else if (animation.timer < animation.start && animation.speed < 0) + { + if (animation.IsLooped()) + { + animation.timer = animation.end; + for (auto& channel : animation.channels) + { + channel.next_event = 0; + } + } + else if (animation.IsPingPong()) + { + animation.timer = animation.start; + animation.speed = -animation.speed; + } + else + { + animation.timer = animation.start; + animation.Pause(); } } diff --git a/WickedEngine/wiScene_BindLua.cpp b/WickedEngine/wiScene_BindLua.cpp index 37f5d59dd..29b03e052 100644 --- a/WickedEngine/wiScene_BindLua.cpp +++ b/WickedEngine/wiScene_BindLua.cpp @@ -4010,6 +4010,10 @@ Luna::FunctionType AnimationComponent_BindLua::metho lunamethod(AnimationComponent_BindLua, SetStart), lunamethod(AnimationComponent_BindLua, GetEnd), lunamethod(AnimationComponent_BindLua, SetEnd), + lunamethod(AnimationComponent_BindLua, SetPingPong), + lunamethod(AnimationComponent_BindLua, IsPingPong), + lunamethod(AnimationComponent_BindLua, SetPlayOnce), + lunamethod(AnimationComponent_BindLua, IsPlayingOnce), lunamethod(AnimationComponent_BindLua, IsRootMotion), lunamethod(AnimationComponent_BindLua, RootMotionOn), lunamethod(AnimationComponent_BindLua, RootMotionOff), @@ -4143,6 +4147,35 @@ int AnimationComponent_BindLua::SetEnd(lua_State* L) } return 0; } +int AnimationComponent_BindLua::SetPingPong(lua_State* L) +{ + int argc = wi::lua::SGetArgCount(L); + if (argc > 0) + { + bool value = wi::lua::SGetBool(L, 1); + component->SetPingPong(value); + } + else + { + wi::lua::SError(L, "SetPingPong(bool value) not enough arguments!"); + } + return 0; +} +int AnimationComponent_BindLua::IsPingPong(lua_State* L) +{ + wi::lua::SSetBool(L, component->IsPingPong()); + return 1; +} +int AnimationComponent_BindLua::SetPlayOnce(lua_State* L) +{ + component->SetPlayOnce(); + return 0; +} +int AnimationComponent_BindLua::IsPlayingOnce(lua_State* L) +{ + wi::lua::SSetBool(L, component->IsPlayingOnce()); + return 1; +} int AnimationComponent_BindLua::IsRootMotion(lua_State* L) { diff --git a/WickedEngine/wiScene_BindLua.h b/WickedEngine/wiScene_BindLua.h index 45644c71c..1a786c260 100644 --- a/WickedEngine/wiScene_BindLua.h +++ b/WickedEngine/wiScene_BindLua.h @@ -368,6 +368,10 @@ namespace wi::lua::scene int SetStart(lua_State* L); int GetEnd(lua_State* L); int SetEnd(lua_State* L); + int SetPingPong(lua_State* L); + int IsPingPong(lua_State* L); + int SetPlayOnce(lua_State* L); + int IsPlayingOnce(lua_State* L); // For Rootmotion int IsRootMotion(lua_State* L); diff --git a/WickedEngine/wiScene_Components.h b/WickedEngine/wiScene_Components.h index afa8d2d0e..a0b398e04 100644 --- a/WickedEngine/wiScene_Components.h +++ b/WickedEngine/wiScene_Components.h @@ -1342,7 +1342,8 @@ namespace wi::scene EMPTY = 0, PLAYING = 1 << 0, LOOPED = 1 << 1, - ROOT_MOTION = 1 << 2 + ROOT_MOTION = 1 << 2, + PING_PONG = 1 << 3, }; uint32_t _flags = LOOPED; float start = 0; @@ -1481,6 +1482,8 @@ namespace wi::scene inline bool IsPlaying() const { return _flags & PLAYING; } inline bool IsLooped() const { return _flags & LOOPED; } + inline bool IsPingPong() const { return _flags & PING_PONG; } + inline bool IsPlayingOnce() const { return (_flags & (LOOPED | PING_PONG)) == 0; } inline float GetLength() const { return end - start; } inline bool IsEnded() const { return timer >= end; } inline bool IsRootMotion() const { return _flags & ROOT_MOTION; } @@ -1488,7 +1491,9 @@ namespace wi::scene 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 SetLooped(bool value = true) { if (value) { _flags |= LOOPED; _flags &= ~PING_PONG; } else { _flags &= ~LOOPED; } } + inline void SetPingPong(bool value = true) { if (value) { _flags |= PING_PONG; _flags &= ~LOOPED; } else { _flags &= ~PING_PONG; } } + inline void SetPlayOnce() { _flags &= ~(LOOPED | PING_PONG); } inline void RootMotionOn() { _flags |= ROOT_MOTION; } inline void RootMotionOff() { _flags &= ~ROOT_MOTION; }