# LibGDX Invalid animation during actor rotation

I have a problem with the animation when setting the actor's rotation. Every now and then the server sends an updated actor's rotation position. The client uses interpolation to smoothly update the old position to the new one. Everything works fine until the actor should turn between 180° and -180°. In this case, I see unnatural behavior - the actor does a full lap instead of moving a tiny bit. How to fix it?

Render method:

ACTOR.setRotation(ACTOR.getRotation() + (destinationRotation - ACTOR.getRotation()) * 10f * Gdx.graphics.getDeltaTime());


"destinationRotation" is a variable containing the updated rotation position.

I attach a picture to illustrate situation.

Disclaimer: It has been a good while since I've done anything in Java, and that is without considering LibGDX.

We need to make sure we have the shortest way to go from the current_angle to the target_angle. For that we will implement wrap:

float wrap(float value, float lower_bound, float upper_bound)
{
float range = upper_bound - lower_bound;
return value - (range * Math.floor((value - lower_bound) / range));
}


Then we can get our angle offset like this:

float half_turn = 180f; // or Math.PI if we were using radians
float current_angle = ACTOR.getRotation();
float target_angle = destinationRotation;
float offset_angle = wrap(target_angle - current_angle, -half_turn, half_turn);


It should give us the angle difference, the shortest way.

I'm sure there are other ways to do this. I don't know if there is a better one for Java.

We want to apply an angular velocity. So we will compute how much we can rotate:

float delta = Gdx.graphics.getDeltaTime();  // seconds
float rotation_speed = 10f;                 // angle per second
float angle_delta = rotation_speed * delta; // angle


Now, notice that our angle_delta is always positive. But not our offset_angle. So we are going to decompose offset_angle into sign and magnitud:

float offset_angle_magnitud = Math.abs(offset_angle);
float offset_angle_sign = Math.signum(offset_angle);


So we can limit how much we rotate so we don't overshoot, while preserving the sign:

float rotation_angle = Math.min(angle_delta, offset_angle_magnitud) * offset_angle_sign


Finally add that to the current_angle:

ACTOR.setRotation(current_angle + rotation_angle);


I don't know if you would need to wrap at the end there (or if setRotation handles that already):

ACTOR.setRotation(wrap(current_angle + rotation_angle, -half_turn, half_turn));


Or perhaps rotate works better:

ACTOR.rotate(rotation_angle);


The above code has the behavior of a sudden start/stop.

In a more conventional situation we would use an acceleration, but that would require to add state (either keep track of time or the current velocity). However, we cannot simply increase velocity with acceleration… Because we also want to decrease velocity and smoothing reach zero.

Thus, we want a solution with variable acceleration. How do we know if we should increase or reduce the acceleration? Intuition dictates that it should be some function of how far we are from the destination. Well, I'll cut the middle man and make the the velocity a function of the angle (this way our approach can remain stateless). This approach requires four parameters:

• We will specify a range of angles (minimum and maximum) over which we will do easing. Which I'll call min_angle and max_angle.
• Similarly there will be a range of angular velocities (minimum and maximum). Which I'll call min_rotation_speed and max_rotation_speed.

For practical proposes, the minimum angle and the minimum angular velocity will be zero. Yet, I'll leave the variables in for reference. Feel free to optimize once you have a solution that behaves the way you want.

We need to compute the size of the angle range:

float angle_range = max_angle - min_angle


I'm assuming that max_angle > min_angle, if that is not the case you can swap them. We should have a positive angle_range.

We will only do easing if this angle_range is not zero:

if (angle_range > 0f)
{
// …
}


Now, we will map the offset_angle_magnitud to a value between zero and one which indicates where it is in the angle_range:

if (angle_range > 0f)
{
float x = Math.min(
max(offset_angle_magnitud - min_angle, 0.0) / angle_range,
1.0
);
}


This should give you zero below min_angle and one above max_angle, a number in between in the middle.

And now that we have a value between zero and one, we can interpolate:

if (angle_range > 0f)
{
float x = Math.min(
max(offset_angle_magnitud - min_angle, 0.0) / angle_range,
1.0
);
angle_delta = interpolate(min_rotation_speed, max_rotation_speed, x) * delta;
}


Notice than alternative is to use a smoothstep function. But that is just one option, I'll go for something general.

We can write that interpolate in terms of an ease function:

float interpolate(float lower_bound, float upper_bound, float weight)
{
return (upper_bound - lower_bound) * ease(weight) + lower_bound;
}


And there are many options for the ease function. Be aware that this function controls the velocity as a function of the angle. You can start with simply this:

float ease(float x)
{
return x;
}


The above function would make interpolate a linear interpolation (a.k.a lerp).

With that, as the angle to the destination becomes larger, the velocity also becomes larger. Similarly as the angle to destination becomes smaller, the velocity also becomes smaller.

However, my intuition is that your function should have a quadratic behavior. You can try that too:

float ease(float x)
{
return x * x;
}


You can find more easing functions searching online. There are some "cheat sheets" such as this one: https://easings.net - pick, test, choose. Then - if want - you can inline these functions, and you may find some cancellations in the process.

• Thank you very much for your response. The obtained effect fixed the problem. I have a question: is it possible to get smoother movement? It's about smoothing the edges (with "braking rotation"), just like in the basic code posted by me. Commented Jun 1, 2022 at 14:30