[Solved] Rotate towards a target angle

Started by
8 comments, last by Serapth 7 years, 10 months ago

I have an angle that i want an object to rotate towards. My current code is as follows:


if (desiredAngle > angle) {			angle+=0.1f;		} else {			angle-=0.1f;		}

However it has a flaw. The range of values for rotation is -3.14 (pi) to 3.14 (see http://www.mediafire.com/view/r7dgb4z8pgb3p6g/angle+analysis.png for a diagram), so if say my object is at 3.13 radians and the target is -3.13 radians, then the object will spin the long way around to reach the target rotation.

Can someone help me make it so that it will always take the shortest route? This is for a tower defence game so it is pretty important that the towers rotate sensibly. Thanks for your efforts in advance.

Advertisement

First, stop using negative.

Make your full circle between 2Pi and zero. Then if it goes below zero or more than 2Pi divide the rotation by 2Pi (a full circle). The remainder will be how far you actually rotate.

So, if you are at Pi/4 (an 8th of a circle) and the rotation is to subtract Pi/2 (a quarter circle) that's below 2Pi and so the entire thing is remainder and the remainder is Pi/2. That would give you negative Pi/4 which we don't have. You rotate to zero and then move back to 2Pi at zero. So, that negative amount get subtracted from 2Pi.

Basically, check if the result is greater than 2Pi or less than zero. Greater than 2Pi, (after dividing and getting the remainder if it turns more than a full circle) subtract 2Pi from the result and rotate that much from zero. Less than 0, take the negative amount and rotate that much from 2Pi; in other words, subtract that value from 2Pi.

To determine the shortest rotation to target

Let's say the target is at 15/8Pi. That's basically -1/8Pi or about 1/16th of a circle. Now let's say you are facing Pi/2 which is 45 degrees or a quarter circle. I suppose what you need to do is try 15/8Pi and try it's negative inverse which is -1/8Pi. A full circle is 2Pi. So, subtracting 15/8 from 2 gives you 1/8th. That's how you can find the negative angle. Then just subtract Pi/2 from 15/8Pi: 15/8Pi - 4/8Pi = 11/8Pi. And subtract -1/8Pi from Pi/2: 4/8Pi - (-1/8Pi) = 5/8Pi. 5/8Pi is a lot less than 11/8Pi so the 5/8ths Pi is the shortest route.

Use complex numbers with modulus 1 instead of angles. That would be z = cos(alpha) + i * sin(alpha).


if (imag(desired / current) > 0.0)
  current *= Complex(cos(0.1), sin(0.1));
else
  current *= Complex(cos(0.1), -sin(0.1));

It's not just this piece of code: Pretty much anything you want to do with angles is easier with complex numbers.

i have no idea what complex numbers are, also i had to use negative as that's just naturally what java wants it seems, so I had to come up with another solution which seems to be working

basically i check the surrounding universes of (-pi to pi) to check for equivalent radians to my target radians (like how 0 = 2pi = 4pi).


		// get surrounding universe equivalents of desiredAngle
		float desiredAngleM1 = (float) (desiredAngle - 2*Math.PI);
		float desiredAngleP1 = (float) (desiredAngle + 2*Math.PI);

I then compare my current angle to each of those angles and check which one is closest.



		// find closest universe equivalent of desiredAngle to angle
		float dADiff = Math.abs(desiredAngle - angle);
		float dAM1Diff = Math.abs(desiredAngleM1 - angle);
		float dAP1Diff = Math.abs(desiredAngleP1 - angle);
		
		float closestUniverse = desiredAngle;
		float closestDiffToZero = dADiff;
		if (dAM1Diff < closestDiffToZero) {
			closestDiffToZero = dAM1Diff;
			closestUniverse = desiredAngleM1;
		}
		if (dAP1Diff < closestDiffToZero) {
			closestDiffToZero = dAP1Diff;
			closestUniverse = dAM1Diff;
		}

Then i simply use the code from before to rotate towards the target:



		// rotate towards it
		if (closestUniverse > angle) {
			angle+=0.1f;
		} else {
			angle-=0.1f;
		}

I then tried a angle %= 2*Math.Pi but it wasn't working well. I had some issues where if the desired and current angle were less than 0 and the desired angle was greater than the current angle, then the object would rotate to 0 then back to the proper target. This fixed it for me:



	// ensure angle stays under PI
        if (angle > Math.PI) {
            angle -= 2*Math.PI;
        }

Just to make sure the angle doesn't keep oscillating back and forth when the current angle is around the target angle (for example if the current rotation is .5, target is .6, rotation speed is .2 - can't reach .6 so it keeps going back and forth), i added a lock just to fit everything together nicely. Since this is called when the target angle is "reached" i can call my turret shooting code from here when i get around to it.


		// Lock rotation to target if looking close enough to it
		if (Math.abs(angle - closestUniverse) < .2f) {
			angle = desiredAngle;
		}

Full code:


float angle = 0f;
float desiredAngle; // = [some value]

		// find which way to rotate
		// get surrounding universe equivalents of desiredAngle
		float desiredAngleM1 = (float) (desiredAngle - 2*Math.PI);
		float desiredAngleP1 = (float) (desiredAngle + 2*Math.PI);
		
		// find closest universe equivalent of desiredAngle
		float dADiff = Math.abs(desiredAngle - angle);
		float dAM1Diff = Math.abs(desiredAngleM1 - angle);
		float dAP1Diff = Math.abs(desiredAngleP1 - angle);
		
		float closestUniverse = desiredAngle;
		float closestDiffToZero = dADiff;
		if (dAM1Diff < closestDiffToZero) {
			closestDiffToZero = dAM1Diff;
			closestUniverse = desiredAngleM1;
		}
		if (dAP1Diff < closestDiffToZero) {
			closestDiffToZero = dAP1Diff;
			closestUniverse = dAM1Diff;
		}
		
		// rotate towards it
		if (closestUniverse > angle) {
			angle+=0.1f;
		} else {
			angle-=0.1f;
		}
		// ensure angle stays under PI
		if (angle > Math.PI) {
			angle -= 2*Math.PI;
		}
		
		// Lock to target if within this
		if (Math.abs(angle - closestUniverse) < .2f) {
			angle = desiredAngle;
		}

Use complex numbers with modulus 1 instead of angles. That would be z = cos(alpha) + i * sin(alpha).


if (imag(desired / current) > 0.0)
  current *= Complex(cos(0.1), sin(0.1));
else
  current *= Complex(cos(0.1), -sin(0.1));
It's not just this piece of code: Pretty much anything you want to do with angles is easier with complex numbers.


I would upvote this twice if I could.

i have no idea what complex numbers are


You should learn them. They will benefit you greatly in game development and aren't that difficult to pick up.
http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/complex/transforms/
It make take a bit of effort to learn them, but I will pay off big time.
My current game project Platform RPG

Use complex numbers with modulus 1 instead of angles. That would be z = cos(alpha) + i * sin(alpha).


if (imag(desired / current) > 0.0)
  current *= Complex(cos(0.1), sin(0.1));
else
  current *= Complex(cos(0.1), -sin(0.1));
It's not just this piece of code: Pretty much anything you want to do with angles is easier with complex numbers.


I would upvote this twice if I could.

i have no idea what complex numbers are


You should learn them. They will benefit you greatly in game development and aren't that difficult to pick up.
http://www.euclideanspace.com/maths/algebra/realNormedAlgebra/complex/transforms/
It make take a bit of effort to learn them, but I will pay off big time.

if they're that useful i'll be sure to do some research on them later. Thanks

Your code seemed a little complicated, correcting things afterwards, etc, so I had a go at it.
I got a much simpler version in two iterations, I'll show them both or it will look like total magic, perhaps :)

(all code is untested, and may contain errors)


float diffAngle = destAngle - currentAngle;
if (diffAngle > Math.PI) {
    // Rotating towards positive angle inefficiently, rotate the other way around.
    currentAngle = currentAngle - 0.1;
    if (currentAngle < -Math.PI) { currentAngle += 2 * Math.PI; }
} else if (diffAngle < -Math.PI) {
    // Rotating towards negative angle inefficiently, rotate the other way around.
    currentAngle = currentAngle + 0.1;
    if (currentAngle > Math.PI) { currentAngle -= 2 * Math.PI; }
} else {
    // diffAngle direction is fine, just limit to within 0.1 around 0
    diffAngle = Math.min(0.1, diffAngle);
    diffAngle = Math.max(-0.1, diffAngle);
    currentAngle += diffAngle;
}

The idea is that rotating towards the destination angle (destAngle) is always fine, except when the rotation angle is too big or too small (ie more than +/- PI). The first two cases handle rotating the other way around in that case. Note that the next time you evaluate this code, the rotation angle only gets bigger (we rotated in the wrong direction), so it will get caught again in the 'rotate in counter direction', and continue to rotate "in the wrong way".

Eventually, you pass the -PI or +PI angle, and then you'll be in the 'normal' fine way to just rotate towards the destination angle.

The 3rd case handles that. It clips the difference between 0.1 and -0.1, and just adds that to the current angle. Anything bigger than the limit rotates 0.1 in the right direction, anything less than the limit just jumps to the destination angle.

So far so good. A lot better than your solution, but the 2 cases looked a lot like the problem that is being solved in the 3rd case. Also, I'd really like to use that -0.1 and 0.1 limit thing in the first two cases too. After some thinking I came up with this:


float diffAngle = destAngle - currentAngle;
if (diffAngle > Math.PI || diffAngle < -Math.PI) diffAngle = -diffAngle;

diffAngle = Math.min(0.1, diffAngle);
diffAngle = Math.max(-0.1, diffAngle);

currentAngle += diffAngle;
if (currentAngle > Math.PI)  currentAngle -= 2 * Math.PI;
if (currentAngle < -Math.PI) currentAngle += 2 * Math.PI;

After computing the difference, I now decide the direction of movement in 'diffAngle', by reversing its sign if needed.

Then I limit the difference to the -0.1 to 0.1 interval, and add it to the currentAngle.

Since I may be moving in the 'wrong' direction, afterwards, the currentAngle may need adjustment to stay in its limits.

Determine what angle you should be in, it's a simple vector calculation and then atan2.

Then you substract the angle and see what "route" is the smallest, then you add or substract accordingly to rotate the tower to be in the direction of the target.

DTT = direction to target (2D vector)
CH = current heading (2D vector)
CP = cross product of DTT and CH

///////////////

CP = (DTT.x * CH.y) - (DTT.y * CH.x)

if(CP < 0) {
  //turn left
}
else if(CP > 0) {
  //turn right
}
else {
  //you're either pointing straight toward or straight away
  //you can compare signs to find out. if you're pointing towards
  //then the signs of both x's will be the same and the signs of both y's will be the same
  //if not then turn either direction (preferably in the direction toward the target's motion)
}

I don't know the synatx for bit math in Java, but on IEEE floats and doubles you can do a sign equality check with:

if( ((A ^ B) & SIGN_BIT_MASK) != 0) { signs are opposite }
void hurrrrrrrr() {__asm sub [ebp+4],5;}

There are ten kinds of people in this world: those who understand binary and those who don't.

The example code is in JavaScript, but my GameDev Math series covers this and more. Specifically rotating to face another object. Grok the basics first, then move on to more elegant solutions later.

This topic is closed to new replies.

Advertisement