Rotating script woes

Hi guys

I need some help with scripting logic. Ive spent way too much time on this myself with no success.

I have an object that rotates to either one of the following: 0, 45, 90, 135, 180, 224, 270, 315, and back to 0 degrees. I used the Vector3.RotateTowards function, but its only partially useful. For example, If I rotate from 0 degrees to 90 or 180, it does so correctly. But if I rotate to anything over 180, it goes the long way around, passing 90, 180 and stopping at say for example 270. What I want it to do is go back from 0 to 360, 359, 358 etc until it reaches 270, which is only a 90 degree difference. Same with when Im going from 315 to 90 degrees. It counts back from 315, instead of going to 360 and on to 90, ie, the shorter rout.

Can anyone help with pseudocode at least? Im really frustrated with this.

There’s a couple of things you could try. You might take a look at Mathf.DeltaAngle(), which computes the difference between two angles, taking periodicity into account (which is what you’re having problems with). You could also look at Mathf.SmoothDampAngle(), which I would guess also handles periodicity correctly.

Another option would be to build quaternions representing the starting orientation and the target orientation, and interpolate between them using Slerp(). (Assuming the Unity quaternion Slerp() function is implemented in the usual way, this should guarantee that the rotation occurs over the shortest arc.)

Hi Jesse

Thanks for the reply. Sorry I didn’t reply, Ive been quite sick recently.

Im glad that there is hope for my case (pun!)! Though, I still can’t fully get my head around what you were saying. I couldn’t find anything about Quaternions in the manual that explained them to me. I also get the Mathf.DeltaAngle() function but im not sure how to use it for my purposes.

Can you give me a step by step theoretical layout of the code? I will figure out the finer details, I just need help going in the right direction (another pun!)

Thanks!

you can check the animation scripts on the wiki:
http://www.unifycommunity.com/wiki/index.php?title=Scripts#Animation_Controllers

here is an example of a simple Rotation function from the MoveObject script from the wiki:

function Update () {

if(Input.GetMouseButtonDown(0)){
		Rotation(transform,Vector3(0,270,0),3);
	}
if(Input.GetMouseButtonDown(1)){
		Rotation(transform,270,Vector3.up,3);
	}
}

function Rotation (thisTransform : Transform, degrees : Vector3, time : float) {
    var startRotation = thisTransform.rotation;
    var endRotation = thisTransform.rotation * Quaternion.Euler(degrees);
    var rate = 1.0/time;
    var t = 0.0;
    while (t < 1.0) {
        t += Time.deltaTime * rate;
        thisTransform.rotation = Quaternion.Slerp(startRotation, endRotation, t);
        yield;
    }
}

function Rotation (thisTransform : Transform, degrees : float, rotationAxis : Vector3, time : float) {
    var startRotation = thisTransform.rotation;
    var endRotation = thisTransform.rotation * Quaternion.AngleAxis(degrees,rotationAxis);
    var rate = 1.0/time;
    var t = 0.0;
    while (t < 1.0) {
        t += Time.deltaTime * rate;
        thisTransform.rotation = Quaternion.Slerp(startRotation, endRotation, t);
        yield;
    }
}

You may have already gotten the answer you were looking for in the above post, but here’s how the SmoothDampAngle() function works. (Note that I’m assuming SmoothDampAngle() rotates over the shortest arc, although it doesn’t look like it states that explicitly in the documentation.)

The first argument is the angle you want to rotate from; the second is the angle you want to rotate to. The fourth argument is a measure of how fast you want the interpolation to occur. (The fifth and sixth arguments are optional.) The third argument is a reference to a variable that the function uses to keep track of the current velocity. All you need to do is create and supply this variable; you don’t need to set it to any particular value. Here’s a quick example (C#, untested):

float velocity = 0f;

void Update()
{
    float angle = Mathf.SmoothDampAngle(0f, 270f, ref velocity, .1f);
    transform.eulerAngles = new Vector3(0f, 0f, angle);
}

If you’d prefer to rotate from one angle to the other angle in an exactly specified amount of time, you might try this. First, use DeltaAngle() to compute the different between the two angles. Then, increase a variable (t in the following example) from 0 to 1 over the desired time interval, and at each update, do this:

float angle = startAngle + t * deltaAngle;
transform.eulerAngles = new Vector3(0f, 0f, angle);

You could probably also use SmoothStep() to smooth out the rotation (or use your own smoothing function).

Thanks for the replies!

It seems like there are 2 functions that deal with Angle interpolation, SmoothDampAngle() and LerpAngle(). Functions like slerp interpolates a ‘value’, not an ‘angle’. SmoothDampAngle() looks like the most flexible option.

So, now I have the player rotating correctly, but I have 2 strange, what appears to be, bugs. Here is my function.

void RotateMe(float rotateYTarget)
	{		
		if(Mathf.Abs(myGeometry.transform.eulerAngles.y) == rotateYTarget)
			return;

The function is called every frame so I check to see if the player is already in the correct rotation (the function that calls this one handles the player movement). No need for extra function calls down below. This works fine for anything between 360 and 225 degrees, and 135 for some reason. Whenever I rotate to 180, 90 and 45 degrees, this if statement somehow returns false. I even output the result to the console, and it displays “180 Rotating to 180”.

Debug.Log(myGeometry.transform.eulerAngles.y + " Rotating to " + rotateYTarget);

So, both values are equal, yet it somehow returns false if I compare the two in the if statement, so the Debug.Log message is displayed every frame. It should only display every frame while the player is rotating.

The rest of the function.

		rotateTowardsProgess = Mathf.SmoothDampAngle(myGeometry.transform.eulerAngles.y, rotateYTarget, ref nill, rotateTowardsTargetInSeconds);		
		
		if(Mathf.Abs(myGeometry.transform.eulerAngles.y - rotateYTarget) < 0.2f)
			rotateTowardsProgess = rotateYTarget;
			
		myGeometry.transform.eulerAngles = new Vector3 (0f, rotateTowardsProgess, 0f);
			
			
	}

When I rotate to 90 or 45 degrees, it never goes to 90 or 45 but stops at 90.00001 and 45.00001. I can understand that these values don’t return true when compared to the target value.

My questions then are:

  1. Why don’t the numbers settle at 90 and 45 degrees? I snap the result from the SmoothDampAngle function to the target amount if the difference is less than 0.2.

  2. Why does the if statement return false when I compare 2 values (180 and 180) that are the same according to the console output.

My guess is that when you assign a value to transform.eulerAngles, a quaternion is built from those angles, and that later when you ask for the Euler angles back, the quaternion is converted back to Euler angle form, meaning that the resulting angles may not be the same as the input angles (either due to aliasing, or, more likely in this case, due to numerical imprecision).

I could be wrong about this, since I don’t know how Transform tracks its data internally. But, it would seem to be born out simply by observing the inspector for the transform component, which behaves similarly; that is, values such as 0 and 90 will occasionally turn into very small non-zero values or values such as 90.000001 (I’m guessing at the number of zeros there).

These are all of course examples of the vagaries of working with floating-point representations. What I would recommend would be instead to use some sort of deterministic method for determining when the object has reached the target orientation. One method would be to rotate over a fixed time interval, and when the end of the time interval is reached, clamp to the target orientation and you’re done. If that isn’t feasible, another option would be to stop rotating and clamp to the target orientation when the object has rotated past the target angle (which you should be able to determine using the Mathf ‘angle delta’ function).

It may be that the numbers are actually different, but the digits that differ aren’t being displayed in the console.

That makes sense. I didn’t consider number conversions internally.

I think I might try adding some leeway in the data comparison, like a margin of error. Maybe something like 0.1f degrees or so. I then snap the rotation to the desired angle in the rotation function when it reaches a difference of 0.2f, for example. If that makes sense… I have what feels like a slightly enlarged head here. :slight_smile: Thanks for the help so far.