Smoothly translating geometry from A to B over time

Started by
8 comments, last by Dante3085 4 years, 8 months ago

Hello Game Devs,

I am working on a class that is supposed to smoothly translate 2D geometry beteen 2 Points over time. I am not really happy with my solution and wonder if other developers can provide some feedback in terms of other solutions or things they would do different. The following is an example of the current state of my solution.TranslateAnimation.thumb.gif.a5a05aed124f42a1a5a14e4287e15f88.gif

This is the function that executes the Translation algorithm...


void TranslateAnimation::update(sf::Time time)
	{
		if (doUpdate)
		{
			// Compute distance between current position of menuElement and goal position.
			float distance = vectorLength(to - menuElement->getPosition());

			/* If distance is smaller than a minimum,  just place menuElement at goal position
			   and stop the process.
			*/
			if (distance < minDistance)
			{
				menuElement->setPosition(to);
				doUpdate = false;
				finished = true;
				currentVelocity = (to - menuElement->getPosition()) * velocityModifier;

				return;
			}

			/* Move the menuElement relative to it's current position by the current velocity.
			*/
			menuElement->move(currentVelocity);

			// Decrease velocity with each tick.
			currentVelocity *= 0.8f;

			static sf::Vector2f minVelocity{ 10.f, 10.f };

			// If velocity is smaller than a minimum, reset it to it's initial value.
			if (abs(currentVelocity.x) < minVelocity.x || abs(currentVelocity.y) < minVelocity.y)
			{
				currentVelocity = (to - menuElement->getPosition()) * velocityModifier;
			}
		}
	}

The relevant .h and .cpp can be found here: https://github.com/Dante3085/ProjectSpace/blob/developer/src/header/TranslateAnimation.hhttps://github.com/Dante3085/ProjectSpace/blob/developer/src/animation/TranslateAnimation.cpp

 

Thanks!

Advertisement

So what is wrong with it? You could tweak acceleration and perhaps deceleration, but not sure it's even needed.

Like Alberth mentioned, you didn't say what you don't like about your current approach. I'll mention one alternative though, which is to use a mapping function (perhaps more commonly referred to as a 'tweening' function). This would allow you to (more easily) specify a fixed time interval for the animation, to swap in different behaviors, and to eliminate possible ambiguities/vagaries related to time step, thresholds and tolerances, etc.

If you're not already familiar with mapping functions, if you do (e.g.) a Google image search for 'tweening functions' you'll see many images showing graphs of various functions. The most commonly used functions are well documented and are pretty easy to find online. The function most closely matching your example might be 'ease out', of which there are many variations that provide different deceleration behaviors.

34 minutes ago, Zakwayda said:

Like Alberth mentioned, you didn't say what you don't like about your current approach. I'll mention one alternative though, which is to use a mapping function (perhaps more commonly referred to as a 'tweening' function). This would allow you to (more easily) specify a fixed time interval for the animation, to swap in different behaviors, and to eliminate possible ambiguities/vagaries related to time step, thresholds and tolerances, etc.

If you're not already familiar with mapping functions, if you do (e.g.) a Google image search for 'tweening functions' you'll see many images showing graphs of various functions. The most commonly used functions are well documented and are pretty easy to find online. The function most closely matching your example might be 'ease out', of which there are many variations that provide different deceleration behaviors. 

I was just fiddling with numbers and that was the result. Annoyed me a little bit that I didn't have a more clean/concrete approach, but tweening functions are exactly what I was looking for. Thanks!

I should have mentioned that I wanted more control and fixed time interval. I'll make shure to formulate my question better next time.

I am now trying to use an Easing function from Robert Penner(https://github.com/jesusgollonet/ofpennereasing/blob/master/PennerEasing/Quad.cpp). It seems like these functions don't guarantee that the exact destination value will be hit. I had to restrict by using the elapsed time. The values all seemd to be roughly correct. Am I doing something wrong?

 


#include "Game.h"
#include <iostream>

/*
t: elapsedTime (same unit as duration)
b: startValue
c: Difference between endValue and startValue
d: duration
*/
float easeInOut(float t, float b, float c, float d) 
{
	if ((t /= d / 2) < 1) return ((c / 2) * (t * t)) + b;
	return -c / 2 * (((t - 2) * (--t)) - 1) + b;

}

int main()
{
	using namespace ProjectSpace;

    /*Game g{ "ProjectSpace", WindowStyle::DEFAULT };
    g.start();*/

	float val = 0;
	float elapsedMillis = 0;
	float startValue = 0;
	float endValue = 1000;
	float totalMillis = 2000;

	sf::Clock c;
	while (elapsedMillis <= totalMillis)
	{
		val = easeInOut(elapsedMillis, startValue, endValue - startValue, totalMillis);
		std::cout << val << "elapsedMillis: " << elapsedMillis "\n";

		elapsedMillis = c.getElapsedTime().asMilliseconds();
	}

	std::cin.get();
    return 0;
}

That is an output sequence over 1 second.

 

Simply pick a function for the velocity and integrate it to get the distance for a specific time.

E.g. you can use a quadratic function and integrate it, so you'll end up with something like this: https://www.wolframalpha.com/input/?i=integrate+-((0.5-x)*2)^2%2B1 You now just need to scale it so it is equal to 1 at x=1 and then get the distance (from 0 to 1) at the point x=t/duration (only valid if x is in the interval from 0 to 1).

0.5-0.5*cos(t*pi) also works.

8 hours ago, Dante3085 said:

That is an output sequence over 1 second.

Did you mean to include something in the post after that?

Quote

It seems like these functions don't guarantee that the exact destination value will be hit.

Although I'm not entirely sure what you mean by that, I'm guessing maybe you mean that if you submit the 'max time' to the function, you won't necessarily get the exact ending value that you specified.

If that's what you mean, then it's probably not so much that the functions don't guarantee it, but that floating-point math doesn't guarantee it. That is, any function could possibly produce a non-exact 'end' value due to numerical error/imprecision, depending on the intermediate math involved. You could also 'miss' even the approximate end value if the way you're iterating doesn't necessarily hit the 'end' time value. (A typical way to iterate would be to continue until the time exceeds the end time value, and to clamp to that end time value.)

In any case, if you're concerned about the accuracy of the 'end' value, typically you'd just assign/return/whatever that value manually when the time interval has expired. Also, in case some reassurance would be useful, this sort of 'tweening', and the specific functions you're using, are all bog-standard and are used ubiquitously (to the best of my knowledge at least). So don't let the numerical issues (which again aren't specific to these functions) or issues with timing logic deter you.

As an aside, although this code:


if ((t /= d / 2) < 1) return ((c / 2) * (t * t)) + b;
return -c / 2 * (((t - 2) * (--t)) - 1) + b;

Probably works correctly, the way it modifies t in place might warrant attention. Although it may not cause any problems here, there are expressions in which such modifications might lead to incorrect or unexpected results. Just for clarity, I'd revise that code to remove the in-place modifications. (Just my opinion, and tangential.)

As an alternative to the ideas posted here, you could keep track of the current position and velocity, and then move one time step towards the desired position following the differential equation of a damped pendulum. You can control how quickly the destination is reached by tweaking the spring constant. I think the critically damped case should feel nice.

On 8/11/2019 at 11:36 PM, alvaro said:

As an alternative to the ideas posted here, you could keep track of the current position and velocity, and then move one time step towards the desired position following the differential equation of a damped pendulum. You can control how quickly the destination is reached by tweaking the spring constant. I think the critically damped case should feel nice.

Thanks for the reply ! I incorporated the previously mentioned easing/tweening functions(https://github.com/jesusgollonet/ofpennereasing/tree/master/PennerEasing) They provide a lot of variety. I have to say I am pretty happy with them...

easing.gif

This topic is closed to new replies.

Advertisement