-1

I will explain the problem very briefly: In order to get a color animation, the lerp algorithm must be used to change the colors smoothly, which requires the progress of the animation (std::min)((deltaTime / duration), 1.0f) But there is a problem, that by using this animation technique, it runs during the animation duration, but the progress does not reach 1, so that I can kill the timer to avoid additional cpu consumption.

Note that I am using the Direct2D API and the Win32 API for handling my window class.

Before I show you my code, I'll show you how I update my window which you will see in the following codes:

void Window::CreateTimer(bool update, std::uint16_t frameRate, std::uint64_t timerId) 
{ 
    SetTimer(m_Hwnd, timerId, 1000 / frameRate, nullptr); 
    if (update) 
    { 
        HandleTimerEvent([&](const Event& event)
            {
                InvalidateRect(m_hWnd, nullptr, false); 
            });
    }
}

If the update flag was true, we want to handle WM_TIMER to invalidate the window based on the frame rate, which in our case is 60Hz.

Here's the ColorTransition implementaion:

class Transition
{
protected:
    Transition(Window* window, float duration)
        : m_Window(window), m_Duration(duration)
    {
        srand(static_cast<std::uint32_t>(time(nullptr)));
        QueryPerformanceFrequency(&m_Frequency);
        QueryPerformanceCounter(&m_LastFrameTime);
        SetTimerId();
        SetFrameRate();
        m_Window->CreateTimer(true, m_FrameRate, m_TimerId);
    }

public:
    virtual void Update() = 0;

public:
    void SetLastFrameTime()
    {
        m_LastFrameTime = m_CurrentTime;
    }

    void SetTimerId()
    {
        m_TimerId = static_cast<std::uint32_t>(rand());
    }

    void SetFrameRate(std::uint32_t frameRate = 60)
    {
        m_FrameRate = frameRate;
        KillTimer(m_Window->GetWindow(), m_TimerId);
        m_Window->CreateTimer(true, m_FrameRate, m_TimerId);
    }

protected:
    float m_Duration;
    Window* m_Window;
    LARGE_INTEGER m_Frequency, m_LastFrameTime, m_CurrentTime;
    std::uint32_t m_FrameRate, m_TimerId;
    bool m_IsDone;
};

class ColorTransition : public Transition
{
public:
    ColorTransition(Window* window, float duration, D2D1::ColorF start, D2D1::ColorF end)
        : Transition(window, duration), m_Start(start), m_End(end), m_Color(start)
    {
    }

    D2D1::ColorF GetColor() const
    {
        return D2D1::ColorF(
                        m_Color.r / 255.0f,
                        m_Color.g / 255.0f,
                        m_Color.b / 255.0f,
                        m_Color.a / 255.0f);
    }

    void Update() override
    {
        QueryPerformanceCounter(&m_CurrentTime);

        float deltaTime = static_cast<float>(m_CurrentTime.QuadPart - m_LastFrameTime.QuadPart) /
            static_cast<float>(m_Frequency.QuadPart);
        float progress = (std::min)((deltaTime / m_Duration), 1.0f);
        
        m_Color.r = Lerp(m_Color.r, m_End.r, progress);
        m_Color.g = Lerp(m_Color.g, m_End.g, progress);
        m_Color.b = Lerp(m_Color.b, m_End.b, progress);
        m_Color.a = Lerp(m_Color.a, m_End.a, progress);

        if (progress >= 1.0f)
        {
            KillTimer(m_Window->GetWindow(), m_TimerId);
            m_IsDone = true;
        }
    }

    float Lerp(float start, float end, float progress)
    {
        return start + (end - start) * progress;
    }

private:
    D2D1::ColorF m_Start, m_End, m_Color;
};

main.cpp which includes ColorTransition.h:

ColorTransition transition(&window, 1.0f, D2D1::ColorF(0, 55, 255), D2D1::ColorF(255, 0, 0));

window.HandlePaintEvent[&](const Event& event)
    {
        ID2D1HwndRenderTarget* renderTarget = event.GetRenderTarget();
        ID2D1SolidColorBrush* brush = nullptr;
        renderTarget->CreateSolidColorBrush(
            transition.GetColor(),
            &brush
        );

        transition.Update();
        renderTarget->FillRectangle(
            D2D1::RectF(10, 10, 110, 110),
            brush
        );
        transition.SetLastFrameTime();
    });

I guess the code is pretty simple to understand, so I won't explain the code And I guess I've provided enough code for you to understand what my problem really is.

I can use a variable called m_ElapsedTime for example to store the elapsed time since the last frame and check if it is equal to the transition animation which works but stops before the transition is almost complete.

Here's the Update method with the technique I said:

void Update() override
{
    QueryPerformanceCounter(&m_CurrentTime);

    float deltaTime = static_cast<float>(m_CurrentTime.QuadPart - m_LastFrameTime.QuadPart) /
        static_cast<float>(m_Frequency.QuadPart);
    m_Elapsed += deltaTime;
    float progress = (std::min)((deltaTime / m_Duration), 1.0f);

    m_Current.r = Lerp(m_Current.r, m_End.r, progress);
    m_Current.g = Lerp(m_Current.g, m_End.g, progress);
    m_Current.b = Lerp(m_Current.b, m_End.b, progress);
    m_Current.a = Lerp(m_Current.a, m_End.a, progress);

    if (m_Elapsed >= m_Duration)
    {
        Stop();
    }
}

This works but as I said it stops before the transition is almost complete.

I have not been able to find any solution for it. There aren't many details about this kind of animation in my case because I've been researching for a while to find a solution, so I really had to ask a question here. If you know a better algorithm or a solution for it, please help me And please explain why my code doesn't work with the things I provided.

Thank y'all for help.

  • It's not clear why you think your Lerp function is related to killing the timer, that seems based on the value of the performance counter. But you seem to be using the time between frames to calculate your progress when I think it should be time from the start of the animation. – Jonathan Potter Jul 12 '23 at 20:08
  • @JonathanPotter If the progress is equal or greater than 1 it means the transition is done and I have to kill the timer right? And what do you mean by the start of the animation? – Newbie programmer Jul 12 '23 at 20:51
  • Yes but your calculation of progress is based on the time from the last frame; presumably you want your animation to last for more than one frame. When you start your animation you need to save the current time and then calculate the progress from that timestamp. – Jonathan Potter Jul 12 '23 at 21:20
  • @JonathanPotter I know I may sound stupid but I'm sorry, I did what you said, but it seems that the timer ends earlier than the duration of the animation and the colors are not complete. Please, if possible, post the answer so that I can see what the code is – Newbie programmer Jul 13 '23 at 13:09
  • Did you look at the linked question – Rohit Gupta Jul 15 '23 at 04:52
  • @RohitGupta Yes, I looked, but I didn't understand what it has to do with my question, can you explain what it has to do with my question? – Newbie programmer Jul 15 '23 at 10:13
  • @JonathanPotter I really don't know why the question is marked as duplicate. My question has nothing to do with assigning a double to an int divided by another int. My problem is why using this kind of algorithm stops the animation before it is almost complete. I think my question should be reopened. Unfortunately, this is the first time that I am asking a question in Stackoverflow and I see that my question is marked as duplicate, which has nothing to do with the linked question. I tagged you because I only know you can vote to reopen my question. – Newbie programmer Jul 15 '23 at 10:50
  • Yes I was confused by that as well, the linked duplicate didn't seem related at all. – Jonathan Potter Jul 15 '23 at 20:04

1 Answers1

0

Actually, the problem I had, had nothing to do with delta time or elpsed time, the problem was that as the first parameter of the lerp function I was passing m_Color, which made the animation end much earlier, and instead of elpsed time I had to use delta time, which is a wrong method. I should have passed m_Start instead of the first parameter of the lerp method.

Here's the working version of the Update method:

void Update() override
{
    QueryPerformanceCounter(&m_CurrentTime);

    float deltaTime = static_cast<float>(m_CurrentTime.QuadPart - m_LastFrameTime.QuadPart) /
        static_cast<float>(m_Frequency.QuadPart);
    m_ElapsedTime += deltaTime;
    float progress = (std::min)((m_ElapsedTime / m_Duration), 1.0f);
    
    m_Color.r = Lerp(m_Start.r, m_End.r, progress);
    m_Color.g = Lerp(m_Start.g, m_End.g, progress);
    m_Color.b = Lerp(m_Start.b, m_End.b, progress);
    m_Color.a = Lerp(m_Start.a, m_End.a, progress);

    if (progress >= 1.0f)
    {
        KillTimer(m_Window->GetWindow(), m_TimerId);
        m_IsDone = true;
    }
}

So after all the problem was really simple but an annoying one too.