I've myself implemented this using a helper class called InterpolationHelper and a class that handles the actual easing, called InterpolationCurve. In addition, there's an enum InterpolationCurveType, which decides which type of easing is used. You could replace the enum with overloads of the InterpolationCurve, but I felt that going with an enum was the easiest way to implement this, while still supporting inheritance if needed.
class InterpolationHelper { public: InterpolationHelper() {} InterpolationHelper(float duration, const InterpolationCurve& curve) : m_Duration(duration), m_Curve(curve) { m_StartTime = GetCurrentGlobalTime(); m_FinishTime = m_StartTime + duration; // Make sure that the units match up here. } bool IsFinished() const { return GetCurrentGlobalTime() >= m_FinishTime; } bool IsEmpty() const { return m_Duration == 0.0f && m_StartTime == 0.0f && m_FinishTime == 0.0f; } float GetPercentage() const { const float progress = (GetCurrentGlobalTime() - m_StartTime) / (m_Duration); return m_Curve.GetCurveValue(std::clamp(progress, 0.0f, 1.0f)); } protected: private: float m_Duration = 0.0f; float m_StartTime = 0.0f; float m_FinishTime = 0.0f; InterpolationCurve m_Curve; }
So first we declare the class that helps you with the actual interpolation helper. You would instantiate an InterpolationHelper in your class, and pair it up with some start/begin values, and use the value returned by GetPercentage() as your Lerp progress value. The pattern which I use in my code very often looks something like this:
if (!m_CircleInterpolationHelper.IsEmpty()) { myCircleRadius = Lerp(0.0f, 25.0f, m_CircleInterpolationHelper.GetPercentage()); // Clears the interpolation once done. if (m_CircleInterpolationHelper.IsFinished()) { m_CircleInterpolationHelper = InterpolationHelper() } }
One thing you really want to watch out for is only instantiating the interpolation once you want your animation to start, so for an example you would create your interpolation helper when the menu is opened.
void Editor::OnMenuOpen() { m_CircleInterpolationHelper = InterpolationHelper(1.0f, InterpolationCurve(EInterpolationCurveType::LINEAR)); }
So, now we just need to take care of the actual easing implementation. This is just a wrapper class for your easings. For the linear type, we can just fall through with the progress value fed to the function. You can add whatever math-curves you'd like to use for your interpolations to the case-statement.
enum class InterpolationCurveType { LINEAR, SMOOTHSTEP } class InterpolationCurve { public: InterpolationCurve(InterpolationCurveType type) : m_Type(type) {} float GetCurveValue(float progress) const { switch(m_Type) { case InterpolationCurveType::LINEAR: return progress; case InterpolationCurveType::SMOOTHSTEP: return progress * progress * (3 - 2 * progress); // More cases here, maybe allow sampling a bezier curve or something other cool. } return 0.0f; } protected: private: InterpolationCurveType m_Type; }
As a final disclaimer, the code above is untested and might not work right out of the box, but I'm sure the idea behind the code gets through.