Click here to Skip to main content
15,886,919 members
Please Sign up or sign in to vote.
0.00/5 (No votes)
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:
C++
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:
C++
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.

What I have tried:

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 and 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 as I said.
Posted
Updated 17-Jul-23 19:38pm

You wil have to dig it out yourself. Start by making more TRACE output with a detailed timestamp. I would use some constant for the timer id. In this code your arent correctly use the timer id. Check it!

tip: start with a low frame rate like 10 frames for the compleation animation. It helps to find some issues in your code.
 
Share this answer
 
One thing to keep in mind is the Windows timer works on multiples of 60Hz or 16.7ms. That is as low as it will go about 60Hz is achievable as is 30Hz.

At the risk of blowing my own horn, I like this class for determining elapsed time : High Resolution Timing[^], because it is very, very easy to use.

For your use, you would need an instance and a starting value. They could look like this :
C++
class Transition
{
protected:
    Elapsed        m_Timer;        // elapsed timer instance
    std::uint32_t  m_FrameRate;    // target frequency of animation
    std::uint32_t  m_FramePeriod;  // period of timer in milliseconds
    double         m_ElapsedTime;  // the time animation has actually run
    double         m_StartTime;    // time animation started
    double         m_Duration;     // time animation is supposed to run in seconds
};

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

// constructor

Transition::Transition( ... )
{
   // other code here

   SetFrameRate();
   m_StartTime = m_Timer.Since( 0 );   // initialize the elapsed timer
}

void Transition::Update()
{
    // obtain elapsed time in seconds
    m_ElapsedTime = m_Timer.Since( m_StartTime );
    if( m_ElapsedTime >= m_Duration )
    {
        // time has expired
        KillTimer( m_Window->GetWindow(), m_TimerId );
        m_IsDone = true;
    }
    else
    {
        // determine ratio of progress
        double progress = m_ElapsedTime / m_Duration;
        // update animation based on progress ratio
    }
}
Is the CreateTimer call correct? In the Windows API, the function takes a period in milliseconds, not a frequency. Does your window class work differently?
 
Share this answer
 
v2
Comments
merano99 20-Sep-23 5:16am    
+5, According to my knowledge, thread change times below 16ms were not possible under Windows, at least in the environment I tested. Even if the PerformanceTimer resolves more precisely, the processing of a message could probably also require this minimum time.
I know this may sound silly, but to fix this you need to replace the color value with the starting color. And the other thing you need to do is to use elapsedTime instead of deltaTime to calculate the progress.

Here's the code:
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);
 
Share this answer
 
Comments
merano99 20-Sep-23 5:33am    
Two comments:
1. thread change times below 16ms are hardly feasible in the default setting under Windows, as far as I know. Working out messages with shorter spacing could cause problems.
2. it is not seen with pleasure, if questioners post their answer as Solutions. If you have solved the problem, it will be gladly accepted above as an extension under the asked question. Then everyone will see it, and you can ask questions about the solution you found.

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900