0
\$\begingroup\$

I want to rotate a sprite so that it faces the mouse cursor/screen touch location. Currently I'm using the following method to compute the angle of rotation. The input position is the target point the sprite must face:

- (void) moveToPosition: (CGPoint)pos
{
    moveTarget = pos;
    const float targetX = (moveTarget.x - self.position.x);
    const float targetY = (moveTarget.y - self.position.y);
    const float angle   = atan2(targetX, targetY);
    targetAngle = ClampF(CC_RADIANS_TO_DEGREES(angle), -180.0f, 180.0f);
}

Then in the update method, which is called every frame, I do a linear interpolation between the target angle and the current, to ensure a smooth rotation:

- (void) update
{
    const float currentAngle = self.rotation;
    if (currentAngle != targetAngle)
    {
        const float newAngle = LerpF(currentAngle, targetAngle, [self getTurnSpeed] * Time.deltaTime);
        self.rotation = newAngle;
    }
}

This setup works OK, except when the target angle gets close to 180/-180 degrees. When it gets there the sprite just spins the whole way around in a very wacky manner. I've recorded a short video to illustrate.

Which would be the best way to handle these cases, when the angle wraps, and fix this weird spin effect caused by it?

\$\endgroup\$
2
\$\begingroup\$

Let's speak radians. Your angle is clamped to a real range and from what I can see from your video I would say between −π/2 and 3π/2. Now you can also view angles in different ways, for example as points on a circle. This is clearly the intuitive way to see your problem : in your case the tip of your ship is the considered “angle point” on the circle. The advantage of viewing angles on a circle is that you have no discontinuities, i.e. that -π/2 and π/2 are actually the same point. This way your problem is obvious :

circle and line

You're trying to interpolate from an angle a little on the right of the south angle—it therefore has a value somewhat larger than -π/2— and an angle a little on the left—it has a value a little less than 3π/2. Look at the circle on the right.

But you're using angle value on the real line (see the right line on the figure), which means that when you interpolate two angles (you current ship rotation and the target ship rotation), you go all way through 0 to connect a value close to -π/2 to a value close to 3π/2 (the gray part of the line in the image). You need to take into consideration the “jump” at 3π/2 = −π/2 of the angle representation to move along the correct path. The correct path is in the black arrow on the line in the image. It corresponds to the path made of the black arrow in the circle. The easiest way is to compute the distance between your start angle and your target angle on the real line (i.e. the absolute value of the difference between the two angles), and check if that distance is larger than π (i.e. larger than a half-circle). If it is, add to the smallest angle. Then you can interpolate the angles and possibly remap the interpolated value to the correct range if required.

\$\endgroup\$
  • \$\begingroup\$ Thanks for the reply. However, I didn't quite understand it. By "computing the absolute difference between the angles", which angles are you referring to? -pi/2 and 3pi/2? Can you provide some algorithm/pseudocode to clarify? Also, not trying to be a smart ass here, but your text is quite confusing at times, missing a lot of 'the' and 'a/an', which makes it really hard to read. The last three lines are specially awkward. If you wrote that late at night, it's understandable, we've all done that sometime ;) \$\endgroup\$ – glampert Apr 2 '14 at 19:11
  • 1
    \$\begingroup\$ I meant take the difference between you current ship rotation (start of the path) and the target ship rotation (end of the path), the take the absolute value of this difference. This is the standard way to compute distance on the real line, but somehow I did not want to bring the distance concept. Here is a simple pseudo-code : if( abs(currentAngle−targetAngle) >= π ) { min(currentAngle,targetAngle) += 2*π; } newAngle = LerpF(currentAngle,targetAngle,deltaT);. I reworded my answer to be a little clearer, hope it helps \$\endgroup\$ – Lærne Apr 3 '14 at 7:05

Not the answer you're looking for? Browse other questions tagged or ask your own question.