1

Situation

I've been hitting my head on this problem for a while now. Basically, I have an angle r (radians), that I want to change to a desired angle wr (radians) with a set speed dr (radians / second), during each step of a simulation. Thus I need that for each step, I have a new angle nr, where nr = r + dr * dt and dt is the delta time since the last step.

Angles are in the space [-pi, pi] and the delta angle is determined by the shortest way to turn. For each step, the delta angle is added to the current rotation, wrapped to [-pi, pi] and stored as the new angle.

Since I haven't got infinite precision in my steps, I will obviously - virtually - never hit the desired angle straight on, thus I need to find when the desired rotation has been reached - and crossed - and then stop rotating and set the angle to the desired angle. In pseudocode:

if rotationSpeed!= 0
     angle = WrapRotation(angle + rotationSpeed * deltaTime)
     if desiredAngle has been reached or crossed
       angle = desiredAngle
       rotationSpeed = 0

Problem

In almost all circumstances, this is easy to do, but when you have an angle high or low enough (close to -pi or pi) and the new angle takes you across the "border", so to speak, things get complicated. All of my attempts have failed to cover all possible situations on current, previous and desired rotation. So I'm asking you if you happen to know a solution for this?

If needed, I've attached my code (C#) below:

// Sets the desired rotation and rotation speed in radians/second:
public void SetRotation(float rotation, float speed)
{
        desiredRotation = MathHelper.WrapAngle(rotation);
        if (desiredRotation != this.rotation)
        {
            // Determine the shortest way to turn (indicated by the sign of speed)
            float a = desiredRotation - this.rotation;
            if (a > Math.PI) a -= 2 * (float)Math.PI;
            if (a < -Math.PI) a += 2 * (float)Math.PI;

            desiredRotationSpeed = a < 0 ? -speed : speed;
        }
 }

 // Update is called per each step. Takes in the amount of seconds since the last call.
 public void Update(float seconds)
 {
        /* Other stuff */
        if (desiredRotationSpeed != 0)
        {
            float delta = desiredRotationSpeed * seconds;
            rotation = MathHelper.WrapAngle(rotation + delta);
            if( /* This is the part I'm struggling with. */ )
            {
                desiredRotationSpeed = 0f;
                rotation = desiredRotation;
            }
        }
        /* Other stuff */
 }

Under normal circumstances (non-cylic behaviour) the following works:

if (Math.Abs(value - desiredValue) < Math.Abs(deltaValue))
    /* Desired has been reached. Do stuff! */

So to clarify. I want to find when the desired angle has been hit (and surpassed due to the precision), so that I can set my current angle to the desired, and stop rotating.

Thanks a lot for your help! I have a feeling that this really has a simple solution! :3

Vectovox
  • 768
  • 1
  • 9
  • 21
  • Can you please use the word 'angle' instead of 'rotation?' When you use 'rotation' it sounds like you're talking about the verb name, rather than the angle. Oh, by the way, this is important, your equality check needs to be customized so you can set the desired accuracy etc, yourself. Otherwise you might never reach the stopping condition. Or maybe I'm just confused about what is it you want to do. Sorry. – GregRos May 30 '12 at 16:35
  • That is true. Changed a lot of them to angle now and kept rotation for the act of rotating. Also, that is exactly what I wanted help with. I don't really know how to detect that the stopping condition has occured due to the cyclic nature of a rotation. The border -pi/pi is what is, to me, confusing. I calculate the new angle for each step, and need to determine if I've crossed (or hit) the desired angle. Sorry, I just find this one hard to explain! :) – Vectovox May 30 '12 at 16:58

2 Answers2

2

Why do you cross the desired rotation? Why not calculate the delta as the minimum of the angle between the current angle and the desired angle and the delta you'd like to move? That way the last step moves you to exactly the desired angle.

I would also change your code so that SetRotation does nothing but store the two values desiredRotation and speed and then the calculation in Update can compare the desired angle with the current angle, calculate the difference, limit that to the speed value x the number of seconds and then move by that amount (with the appropriate if statement for the direction +ve or -ve).

Ian Mercer
  • 35,804
  • 6
  • 87
  • 121
  • Yeah, I tried that, but I do not really know how to find the shortest angle. Consider this: When the desired angle is at -2 radians and the current angle is at PI radians, the shortest way to turn is positive and is about 1.14 radians. I'm not really sure how to calculate the actual distance between two angles I guess. – Vectovox May 30 '12 at 17:06
  • Thanks! That got me thinking in the right direction! Will update this question ASAP with my answer! :) – Vectovox May 30 '12 at 17:16
0

Thanks to Ian Mercer, I figured out what to do.

As in the case with the ordinary number:

if (Math.Abs(value - desiredValue) < Math.Abs(deltaValue)) 
    /* Desired has been reached. Do stuff! */ 

All I needed was some trigonometry to find the smallest angle between the angles. See the two angles as vectors pointing out from origo (or rather two vectors with the angles between them and the x-axis), the smallest angle is pretty simple to find then. Consider the following:

Math.Atan2(Math.Sin(toAngle - fromAngle), Math.Cos(toAngle - fromAngle))

That will give me the smallest angle. Finding out when to stop rotating is just a matter of seeing if the rotated distance is larger than the smallest distance/angle between the two angles.

Making the following change to the code above will solve the problem:

  double distance = Math.Atan2(Math.Sin(desiredRotation - rotation),
       Math.Cos(desiredRotation - rotation));
  if(Math.Abs(distance) < Math.Abs(delta))
  {
       desiredRotationSpeed = 0f;
       rotation = desiredRotation;
  }

I wish I had consulted old dear George Pólya for this one. Thanks for the help people, I needed the push in the right direction!

Vectovox
  • 768
  • 1
  • 9
  • 21
  • 1
    You shouldn't need to use any trigonometry to find the smallest angle between two angles! http://stackoverflow.com/a/7869457/224370 – Ian Mercer May 30 '12 at 18:18