Angles, Eulers, and Rotations

I had occasion to try to help a friend on the NCI Scripters list. They were starting out to figure out how to rotate something, and having a little trouble. I always have trouble when I get back to doing rotations after a time away from them, so I can relate.

My friend was editing an object and trying to figure out how to rotate one of its component objects so that one would look right. They were using the object editor, and looking at the “rotation” field in it. In this picture of a clock face and pointer that I’m using for illustration, you can see that the big red hand is on the 9, and the angle in the editor says

<0, 90, 0>.

That already needs a bit of explanation.

The angle is 90 because of the orientation of my clock. The face is oriented on the X coordinate of the land I’m on. The 3 is at about X = 70 in the region, and the 9 is at about X = 74. So (perhaps unfortunately) the X coordinate is increasing from right to left. This is all part of the joy of coordinates in Second Life, I guess.

Anyway, since the clock face is oriented along X, that means that Y is coming out of the screen toward us, and that means that rotating counter-clockwise will increase the Y coordinate of the rotation field in the editor.

So far so confusing

Now, I think my friend wanted to learn how to make a script set the angle of her component object. Her first thought was to look at what the rotation field said in the editor, and set that into the object’s rotation with a script. All very well and good, except that scripts won’t let you do that. You set an object’s rotation using a rotation variable. If you explore what they are, you find that they are kind of like a vector, but they have four components, not three.

We might hope that we could just put our degrees in there, maybe like

<0, 90, 0, something>

but that is not to be. We can, however, get the corresponding rotation to a vector like

<0, 90, 0>

using llEuler2Rot, which converts what LSL calls an “Euler” angle to a rotation variable. However, it’s not that easy.

LSL, for reasons we’ll not go into here, uses “radians” to define angles, not degrees. There are 360 degrees in a circle but there are 2 PI radians. Basically, that number is chosen because if you were to roll a wheel whose radius was 1, when it went all the way around, it would have rolled a distance of 2 PI, because, remember, C = 2*PI*r, circumference is 2 times PI times the radius. Anyway, SL chose radians for their angles, which is kind of the OK math thing to do, so to convert our

<0, 90, 0>

to radians, we have to multiply it times the built-in constant DEG_TO_RAD (degrees to radians). So we could say

default {
  state_entry() {
    vector angle = <0, 90, 0>;
    vector radians = angle*DEG_TO_RAD;
    llOwnerSay( (string) radians);
  }
}

And if we do, we’ll get

<0.00000, 1.57080, 0.00000>.

How does 90 degrees turn into that? Well, 90 degrees is 1/4 of the way around and 2 PI is about 6.28, and so 1/4 of that is 1.57, so there you are. Not very convenient, but true. Don’t worry, it gets worse.

So, if we had this weird converted angle, could we set our object’s rotation to match it? Yes, we could. We could say:

default {
  state_entry() {
    vector angle = <0, 90, 0>;
    vector radians = angle*DEG_TO_RAD;
    rotation rot = llEuler2Rot(radians);
    llSetRot(rot);
  }
}

And sure enough, the hand would go on the 9. Of course it was already there, but if we try, say,

<0, 120, 0>

it’ll go to the 8.

Now, this is very important: rotations in second life are quite tricky, not because the SL people were trying to irritate us, but because rotations in geometry are actually very complicated. We’re not going to go deeply into that right now, but just be aware there are local rotations, global rotations, and you can rotate a vector by multiplying it by a rotation, and it even makes a difference whether you multiply on left or right. Yes, v/*r is not the same as r*v. This all makes mathematical sense but if, unlike me, you’re not into math, it’s mostly confusing.

The good news is that LSL follows its rules very precisely, and if you write the right script, the rotations will do your bidding. The bad news is that you may often be confused. Then, one day, if you use them enough, things will click.

But that’s not our topic for today. My friend went down a different path, and it was confusing.

It turns out we can get our vector angle thing back from the rotation using llRot2Euler() like this:

default {
  state_entry() {
    vector angle = <0, 90, 0>;
    vector radians = angle*DEG_TO_RAD;
    rotation rot = llEuler2Rot(radians);
    vector back = llRot2Euler(rot)*RAD_TO_DEG;
    llOwnerSay("angle " + (string) angle + " back " + (string) back);
    llSetRot(rot);
  }
}

If we run that, it says:

angle <0.00000, 90.00000, 0.00000> back <0.00000, 90.00000, 0.00000>

And that makes us happy. We can see that what we did worked. But wait. What happens if we use a different angle? What if we set our angle to 270, which should go around and point to 3?

default {
  state_entry() {
    vector angle = <0, 270, 0>;
    vector radians = angle*DEG_TO_RAD;
    rotation rot = llEuler2Rot(radians);
    vector back = llRot2Euler(rot)*RAD_TO_DEG;
    llOwnerSay("angle " + (string) angle + " back " + (string) back);
    llSetRot(rot);
  }
}

The hand does point to the 3 … but the printout says this:

<0.00000, 270.00000, 0.00000> back <0.00000, -90.00000, 0.00000>

OMG! We didn’t get our input back. Instead, we got -90! What’s up with that?

Well, it’s “simple”, but like lots of simple things, also confusing. We moved our hand by going from the zero position to the 270 position, going around counter-clockwise, because that’s how SL angles go. But once the hand was there, set by the rotation, the rotation just knows where it is, not how it got there.

So when we say llRot2Euler it doesn’t know if we went the long way (270), or the short way (-90). So it picks one, and in my experience, it usually picks the shortest one.

We can show this in a fun way. What if we went around to the 9 by going all the way around and then 90 degrees more? That would be 360+90, or 450 degrees:

default {
  state_entry() {
    vector angle = <0, 450, 0>;
    vector radians = angle*DEG_TO_RAD;
    rotation rot = llEuler2Rot(radians);
    vector back = llRot2Euler(rot)*RAD_TO_DEG;
    llOwnerSay("angle " + (string) angle + " back " + (string) back);
    llSetRot(rot);
  }
}

And that prints:

angle <0.00000, 450.00000, 0.00000> back <0.00000, 90.00000, 0.00000>

SL just knows we are at 90. It doesn’t know we went all the way around.

So, my friend had been hoping, and expecting, that when you did llEuler2Rot and then llRot2Euler, you’d get back what you put in. Well, it turns out you don’t. And, since the most efficient way to go is not to go the long way around the block, it even kind of makes sense.

But like it or not, the fact is that doing

llRot2Euler(llEuler2Rot(someAngle))

is going to return the smallest deviation from zero that it can produce, not exactly what we put into it.

It seems to me to make a kind of sense. My friend had trouble with it. I hope this article will help with that.

If I were to draw a general conclusion about all this, I guess it would be that in LSL, as in most of life, we have to figure out what’s really going on and deal with that. We can wish things were different, but often it’s more important to sort out what’s really going on and deal with that.

Happy scripting!!

Leave a Reply

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

WordPress.com Logo

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

Facebook photo

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

Connecting to %s

Blog at WordPress.com.

Up ↑

%d bloggers like this: