mirror of
https://github.com/hyprwm/hyprutils.git
synced 2026-06-23 03:08:24 +02:00
animations: do not floor spring step dt
This commit is contained in:
parent
8597dd96de
commit
fd23d767b0
3 changed files with 108 additions and 122 deletions
|
|
@ -1,6 +1,7 @@
|
|||
#include <hyprutils/animation/AnimatedVariable.hpp>
|
||||
#include <hyprutils/animation/AnimationManager.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include "animation/Spring.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cmath>
|
||||
|
|
@ -15,54 +16,6 @@ static constexpr std::string_view SPRINGPREFIX = "spring:";
|
|||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
||||
static void advanceSpring(float& value, float& velocity, const SSpringCurve& spring, float dt) {
|
||||
if (dt <= 0.F)
|
||||
return;
|
||||
|
||||
const float MASS = std::max(spring.mass, 0.0001F);
|
||||
const float STIFFNESS = std::max(spring.stiffness, 0.0001F);
|
||||
const float DAMPING = std::max(spring.damping, 0.F);
|
||||
|
||||
const float DISPLACEMENT = value - 1.F;
|
||||
const float OMEGA0 = std::sqrt(STIFFNESS / MASS);
|
||||
const float GAMMA = DAMPING / (2.F * MASS);
|
||||
|
||||
if (GAMMA < OMEGA0) {
|
||||
const float OMEGAD = std::sqrt((OMEGA0 * OMEGA0) - (GAMMA * GAMMA));
|
||||
const float EXP = std::exp(-GAMMA * dt);
|
||||
const float SIN = std::sin(OMEGAD * dt);
|
||||
const float COS = std::cos(OMEGAD * dt);
|
||||
|
||||
const float NEW_DISPLACEMENT = EXP * ((DISPLACEMENT * COS) + (((velocity + (GAMMA * DISPLACEMENT)) / OMEGAD) * SIN));
|
||||
const float NEW_VELOCITY = EXP * ((velocity * COS) - (((GAMMA * velocity) + (OMEGA0 * OMEGA0 * DISPLACEMENT)) / OMEGAD) * SIN);
|
||||
|
||||
value = 1.F + NEW_DISPLACEMENT;
|
||||
velocity = NEW_VELOCITY;
|
||||
return;
|
||||
}
|
||||
|
||||
const float CRITICAL_EPSILON = std::max(OMEGA0, 1.F) * 0.0001F;
|
||||
if (std::abs(GAMMA - OMEGA0) <= CRITICAL_EPSILON) {
|
||||
const float EXP = std::exp(-GAMMA * dt);
|
||||
const float B = velocity + (GAMMA * DISPLACEMENT);
|
||||
|
||||
value = 1.F + (EXP * (DISPLACEMENT + (B * dt)));
|
||||
velocity = EXP * (velocity - (GAMMA * B * dt));
|
||||
return;
|
||||
}
|
||||
|
||||
const float ROOT = std::sqrt((GAMMA * GAMMA) - (OMEGA0 * OMEGA0));
|
||||
const float R1 = -GAMMA + ROOT;
|
||||
const float R2 = -GAMMA - ROOT;
|
||||
const float A = (velocity - (R2 * DISPLACEMENT)) / (R1 - R2);
|
||||
const float B = DISPLACEMENT - A;
|
||||
const float E1 = std::exp(R1 * dt);
|
||||
const float E2 = std::exp(R2 * dt);
|
||||
|
||||
value = 1.F + (A * E1) + (B * E2);
|
||||
velocity = (A * R1 * E1) + (B * R2 * E2);
|
||||
}
|
||||
|
||||
void CBaseAnimatedVariable::create(CAnimationManager* pManager, int typeInfo, SP<CBaseAnimatedVariable> pSelf) {
|
||||
m_Type = typeInfo;
|
||||
m_pSelf = std::move(pSelf);
|
||||
|
|
@ -170,16 +123,10 @@ CBaseAnimatedVariable::SCurveStepResult CBaseAnimatedVariable::getCurveStep() {
|
|||
return {.value = 1.F, .finished = true};
|
||||
|
||||
const auto NOW = std::chrono::steady_clock::now();
|
||||
float dt = std::chrono::duration<float>(NOW - springLastStep).count();
|
||||
float dt = Details::springDeltaTime(NOW, springLastStep);
|
||||
springLastStep = NOW;
|
||||
|
||||
constexpr float MINDELTA = 1.F / 240.F;
|
||||
if (dt <= 0.F)
|
||||
dt = MINDELTA;
|
||||
else
|
||||
dt = std::max(dt, MINDELTA);
|
||||
|
||||
advanceSpring(m_fSpringValue, m_fSpringVelocity, *SPRING, dt);
|
||||
Details::advanceSpring(m_fSpringValue, m_fSpringVelocity, *SPRING, dt);
|
||||
|
||||
const bool FINISHED = std::abs(1.F - m_fSpringValue) <= SPRING->valueEpsilon && std::abs(m_fSpringVelocity) <= SPRING->velocityEpsilon;
|
||||
if (FINISHED) {
|
||||
|
|
|
|||
61
src/animation/Spring.hpp
Normal file
61
src/animation/Spring.hpp
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
#pragma once
|
||||
|
||||
#include <hyprutils/animation/AnimationManager.hpp>
|
||||
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <cmath>
|
||||
|
||||
namespace Hyprutils::Animation::Details {
|
||||
inline float springDeltaTime(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point last) {
|
||||
return std::max(std::chrono::duration<float>(now - last).count(), 0.F);
|
||||
}
|
||||
|
||||
inline void advanceSpring(float& value, float& velocity, const SSpringCurve& spring, float dt) {
|
||||
if (dt <= 0.F)
|
||||
return;
|
||||
|
||||
const float MASS = std::max(spring.mass, 0.0001F);
|
||||
const float STIFFNESS = std::max(spring.stiffness, 0.0001F);
|
||||
const float DAMPING = std::max(spring.damping, 0.F);
|
||||
|
||||
const float DISPLACEMENT = value - 1.F;
|
||||
const float OMEGA0 = std::sqrt(STIFFNESS / MASS);
|
||||
const float GAMMA = DAMPING / (2.F * MASS);
|
||||
|
||||
if (GAMMA < OMEGA0) {
|
||||
const float OMEGAD = std::sqrt((OMEGA0 * OMEGA0) - (GAMMA * GAMMA));
|
||||
const float EXP = std::exp(-GAMMA * dt);
|
||||
const float SIN = std::sin(OMEGAD * dt);
|
||||
const float COS = std::cos(OMEGAD * dt);
|
||||
|
||||
const float NEW_DISPLACEMENT = EXP * ((DISPLACEMENT * COS) + (((velocity + (GAMMA * DISPLACEMENT)) / OMEGAD) * SIN));
|
||||
const float NEW_VELOCITY = EXP * ((velocity * COS) - (((GAMMA * velocity) + (OMEGA0 * OMEGA0 * DISPLACEMENT)) / OMEGAD) * SIN);
|
||||
|
||||
value = 1.F + NEW_DISPLACEMENT;
|
||||
velocity = NEW_VELOCITY;
|
||||
return;
|
||||
}
|
||||
|
||||
const float CRITICAL_EPSILON = std::max(OMEGA0, 1.F) * 0.0001F;
|
||||
if (std::abs(GAMMA - OMEGA0) <= CRITICAL_EPSILON) {
|
||||
const float EXP = std::exp(-GAMMA * dt);
|
||||
const float B = velocity + (GAMMA * DISPLACEMENT);
|
||||
|
||||
value = 1.F + (EXP * (DISPLACEMENT + (B * dt)));
|
||||
velocity = EXP * (velocity - (GAMMA * B * dt));
|
||||
return;
|
||||
}
|
||||
|
||||
const float ROOT = std::sqrt((GAMMA * GAMMA) - (OMEGA0 * OMEGA0));
|
||||
const float R1 = -GAMMA + ROOT;
|
||||
const float R2 = -GAMMA - ROOT;
|
||||
const float A = (velocity - (R2 * DISPLACEMENT)) / (R1 - R2);
|
||||
const float B = DISPLACEMENT - A;
|
||||
const float E1 = std::exp(R1 * dt);
|
||||
const float E2 = std::exp(R2 * dt);
|
||||
|
||||
value = 1.F + (A * E1) + (B * E2);
|
||||
velocity = (A * R1 * E1) + (B * R2 * E2);
|
||||
}
|
||||
}
|
||||
|
|
@ -6,9 +6,9 @@
|
|||
#include <hyprutils/animation/AnimatedVariable.hpp>
|
||||
#include <hyprutils/memory/WeakPtr.hpp>
|
||||
#include <hyprutils/memory/UniquePtr.hpp>
|
||||
#include "animation/Spring.hpp"
|
||||
|
||||
#include <chrono>
|
||||
#include <thread>
|
||||
|
||||
#define SP CSharedPointer
|
||||
#define WP CWeakPointer
|
||||
|
|
@ -394,76 +394,54 @@ TEST(Animation, animation) {
|
|||
EXPECT_EQ(pAnimationManager.get(), nullptr);
|
||||
}
|
||||
|
||||
TEST(Animation, springRetargetPreservesVelocity) {
|
||||
config();
|
||||
TEST(Animation, springDeltaUsesElapsedTime) {
|
||||
const auto START = std::chrono::steady_clock::time_point{} + std::chrono::seconds(1);
|
||||
|
||||
pAnimationManager->addSpringWithName("momentum",
|
||||
{
|
||||
.stiffness = 120.f,
|
||||
.damping = 8.f,
|
||||
.mass = 1.f,
|
||||
.valueEpsilon = 0.001f,
|
||||
.velocityEpsilon = 0.001f,
|
||||
});
|
||||
|
||||
animationTree.createNode("spring_global");
|
||||
animationTree.createNode("spring_velocity", "spring_global");
|
||||
animationTree.setConfigForNode("spring_global", 1, 1.f, "spring:momentum");
|
||||
|
||||
PANIMVAR<int> av;
|
||||
pAnimationManager->createAnimation(0, av, "spring_velocity");
|
||||
|
||||
*av = 100;
|
||||
|
||||
int ticks = 0;
|
||||
while (av->value() < 40 && ticks++ < 300) {
|
||||
pAnimationManager->tick();
|
||||
}
|
||||
|
||||
const auto RETARGETPOINT = av->value();
|
||||
EXPECT_GT(RETARGETPOINT, 20);
|
||||
|
||||
PANIMVAR<int> fromRest;
|
||||
pAnimationManager->createAnimation(RETARGETPOINT, fromRest, "spring_velocity");
|
||||
|
||||
*av = 0;
|
||||
*fromRest = 0;
|
||||
EXPECT_EQ(av->value(), RETARGETPOINT);
|
||||
|
||||
pAnimationManager->tick();
|
||||
EXPECT_GT(av->value(), fromRest->value());
|
||||
|
||||
while (pAnimationManager->shouldTickForNext() && ticks++ < 2000) {
|
||||
pAnimationManager->tick();
|
||||
}
|
||||
|
||||
EXPECT_EQ(av->value(), 0);
|
||||
EXPECT_EQ(fromRest->value(), 0);
|
||||
EXPECT_NEAR(Details::springDeltaTime(START + std::chrono::milliseconds(1), START), 0.001F, 0.000001F);
|
||||
EXPECT_NEAR(Details::springDeltaTime(START + std::chrono::milliseconds(120), START), 0.120F, 0.000001F);
|
||||
EXPECT_EQ(Details::springDeltaTime(START, START + std::chrono::milliseconds(1)), 0.F);
|
||||
}
|
||||
|
||||
TEST(Animation, springAdvancesAcrossLateTicks) {
|
||||
config();
|
||||
const SSpringCurve SPRING = {
|
||||
.stiffness = 100.f,
|
||||
.damping = 20.f,
|
||||
.mass = 1.f,
|
||||
.valueEpsilon = 0.001f,
|
||||
.velocityEpsilon = 0.001f,
|
||||
};
|
||||
|
||||
pAnimationManager->addSpringWithName("late",
|
||||
{
|
||||
.stiffness = 100.f,
|
||||
.damping = 20.f,
|
||||
.mass = 1.f,
|
||||
.valueEpsilon = 0.001f,
|
||||
.velocityEpsilon = 0.001f,
|
||||
});
|
||||
float lateValue = 0.F;
|
||||
float lateVelocity = 0.F;
|
||||
float cappedValue = 0.F;
|
||||
float cappedVelocity = 0.F;
|
||||
|
||||
animationTree.createNode("late_tick_global");
|
||||
animationTree.createNode("late_tick", "late_tick_global");
|
||||
animationTree.setConfigForNode("late_tick_global", 1, 1.f, "spring:late");
|
||||
Details::advanceSpring(lateValue, lateVelocity, SPRING, 0.120F);
|
||||
Details::advanceSpring(cappedValue, cappedVelocity, SPRING, 0.050F);
|
||||
|
||||
PANIMVAR<int> av;
|
||||
pAnimationManager->createAnimation(0, av, "late_tick");
|
||||
|
||||
*av = 100;
|
||||
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(120));
|
||||
pAnimationManager->tick();
|
||||
|
||||
EXPECT_GT(av->value(), 25);
|
||||
EXPECT_GT(lateValue, cappedValue);
|
||||
EXPECT_GT(lateValue, 0.25F);
|
||||
}
|
||||
|
||||
TEST(Animation, springDoesNotAdvanceFasterThanElapsedTime) {
|
||||
const SSpringCurve SPRING = {
|
||||
.stiffness = 38.f,
|
||||
.damping = 8.f,
|
||||
.mass = 2.4f,
|
||||
.valueEpsilon = 0.001f,
|
||||
.velocityEpsilon = 0.001f,
|
||||
};
|
||||
|
||||
float repeatedValue = 0.F;
|
||||
float repeatedVelocity = 0.F;
|
||||
float singleValue = 0.F;
|
||||
float singleVelocity = 0.F;
|
||||
|
||||
for (size_t i = 0; i < 132; ++i)
|
||||
Details::advanceSpring(repeatedValue, repeatedVelocity, SPRING, 0.001F);
|
||||
|
||||
Details::advanceSpring(singleValue, singleVelocity, SPRING, 0.132F);
|
||||
|
||||
EXPECT_NEAR(repeatedValue, singleValue, 0.0001F);
|
||||
EXPECT_LT(repeatedValue, 0.5F);
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue