Shortest angle from ‘current’ to ‘desired’ angle

6 08 2008

I figured someone might Google this one one day:

While doing some 3d programming in Python, for FiniteDreams, I was trying to rotate an object from whichever direction that it is currently facing, toward a new desired direction.  I’m only really concerned with “heading“, or “yaw“, which does the side-to-side sweeping in a Z-up coordinate system.

I have a velocity vector built each frame, influenced by whichever combination of directional movement keys are being held.  In order to get my character (or any object with such velocity) to gradually point in the right direction, I had to convert the directional velocity vector into some “heading” value:

>>> from math import atan2
>>> newHeadingAngle = atan2(newVelocityVector.getY(),newVelocityVector.getX()) * (180/pi)

As you can see, atan2 must be imported from the math library.

Next, I have to find the shortest angle between where my current angle is, and where I want to go.  There’s  no sense making my character turn 300 degrees clockwise if he only has to move 60 degrees counter-clockwise:

>>> shortestAngle = newHeadingAngle - currentAngle
>>> if shortestAngle > 180:
...    shortestAngle -= 360
>>> if shortestAngle < -180:
...    shortestAngle += 360

And voila.  Now you’ve got the angle in the range [-180,180], acording to the direction that the object needs to rotate to get to it’s desired heading.

But, we don’t just want to instantly make the character face that direction.  Instead, s/he needs to gradually turn there.  The advantage that we now have is that we have already calculated an angle that needs to be traveled.  All that remains to do is move the character toward it each frame:

>>> movementThisFrame = self.shortestAngle * characterAttribute_speed * elapsedTime
>>> self.shortestAngle -= movementThisFrame
>>> characterNodePath.setH(characterNodePath.getH() + movementThisFrame)

Note that I’ve used the “self” syntax this time around, since I’m getting more specific to the panda3d code.  The “self” variables are persistent in a class object; the context of this code is a panda3d “task“, which is a method that gets executed each frame.  Thus, I need to retain certain of my calculations.  In this case, I need to remember that “shortestAngle” variable, so that each frame can move closer and closer towards it.

The first line takes the character’s speed into account, so that I can have a big fat character turn more slowly than a thin one.  The “elapsedTime” variable is the total time elapsed since the end of the last frame (which I’ve calculated myself).  This regulates the speed at which things happen.  If I do not include this variable, the game would run at terrifyingly unpredictable speeds on different computers, depending on the hardware.

Line 2 subtracts the distance that we want to move this frame, from  total angle that we would like to move towards.

Line 3 actually performs the rotation.  This line does not make a smooth movement out of my new angle, which is why I’m calculating a “movementThisFrame” variable, which is quite small compared to the total angle.  Since it is small, line 3 doesn’t have the visual effect of being choppy or chunky.

Also important is to remember to reduce down the degree of rotation that the character is currently in.  Even though the target angle is reduced, adding an angle of 178 degrees to an existing angle of 164 will add up to 342 degrees of rotation.  The model will still be pointing the right direction, but to stop from winding dozens of times into higher and higher angle numbers, this should be reduced via the same angle-checking method used above, for the “shortestAngle” snippet, two code blocks above.  The only difference would be to use “characterNodePath.setH(characterNodePath.getH() +/- 360)”  If done every frame, the character will always appear to move and point in the right direction.

As one last note, I’ve made a “if” test on the remaining “self.shortestAngle” variable, so that if the remaining angle distance to be covered is less than 0.05 then I just zero it out.




3 responses

11 02 2009

Thank you so much!! I’ve been googling for hours looking to resolve the same problem.

11 02 2009

No problem! I had the same experience, so once I worked it out to something simple, I knew posting it would help someone else 🙂

If anything’s unclear, let me know. It was very directed at Python, and although the code itself isn’t super complex, I’m curious as to the reactions I get.

Thank *you*

10 05 2009

Its amazing how simple things like this can make you lose so much time… Thanks for posting this =)

Leave a Reply

Fill in your details below or click an icon to log in: Logo

You are commenting using your account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: