I recently had to rewrite an LSL function that wasn’t working properly for InWorldz. This involved a bit of vector math and then finally throwing in the creation and use of a rotation quaternion. Since I know that quaternion rotations and the math behind translations between coordinate spaces can seem like voodoo to someone just getting started with games and 3d environments, I figured I’d write this post trying to explain a few of the important topics that were demonstrated while I was implementing the function.

I was tasked to implement a function to take an object’s local +Z axis and point it at another object somewhere in world space. This kind of action is very intuitive to us as human beings. When you want to point at something you simply look in the direction of the object and align your arm, hand, and finger to aim at the target. Our brains perform all the calculations for us, masking the complexity of this seemingly simple action.

Finding the angle between two points can be visualized like the drawing below. We want to rotate A’s forward direction to aim at point B. What’s that? I hear you saying that A is just a point, it has no forward direction. This is true technically, we have to make one up for the object, or determine within our environment which of the object’s local axes is considered forward for pointing. It turns out that in the case I was working, the +Z axis was chosen arbitrarily to be the forward pointing axis for the object. You can see this added to our drawing below. The arrow is now pointing +Z.

Now the picture of what we’re really trying to do should be clearer. We simply want to make that arrow point at the other object. We can think of this two ways. We can either take our current rotation and apply a difference to spin our object from where it is to where we want it to be, or we can forget that we have a current rotation entirely and set our rotation to point at the object. I’m going to look at this in the second way and just figure out if we were pointing to the global forward +Z axis what would be the required rotation to point us at the object.

The first thing we need to do is find the vector that points from A to B. This can be easily done by subtracting the vectors B – A. You can see the new vector pointing from A to B, and that there is an angle between them that we need to find.

You might notice that subtracting A from B means that A is now located at the origin and B is now some offset from the origin. This is a good way to look at the problem to try to determine what to do next.

Now we need to determine the angle between the forward vector A and the point B. We do this using the vector dot product which returns the cosine of the angle between A and B. Remember that since we’re working with A being the new origin, our forward vector is simply the unit vector +Z. Pseudo code below.

Vector3 A, B; //see the drawings //get the directional vector between A and B //another way to look at this is that we're moving B //so that its origin is at A Vector3 targetAtNewOrigin = B - A; Vector3 forward = Vector3.UnitZ; // <0,0,1> //get the cosine of the angle between the forward vector and the target B //which has been moved so that it is relative to A as the new origin float angle = Vector3.Dot(forward, targetAtNewOrigin.Normalize());

Now we have an angle to work with, but there is an important thing to note about what gets returned from the dot product. If Vector3.Dot() returns 1 or larger, it means that the target object is on the specified forward axis. If Vector3.Dot() returns -1 or less, it means that the target object is exactly 180 degrees off our forward axis, behind us. We handle these cases specially.

Quaternion finalRotation; if (angle > 0.999999) { // easy case. we want our final rotation to match // a zero rotation in global space. // object is at our +Z finalRotation = Quaternion.Identity; } if (angle < -0.999999) { // second special case. our final rotation should // be facing -Z. rotate 180 deg on any axis except // Z finalRotation = Quaternion.FromAxisAngle(Vector3.UnitX, PI); }

Note that we’re building a rotation quaternion from an axis/angle representation. In the second case, this rotation could be 180 degrees on either the X or Y axis to point us away from +Z pointing down -Z. To build a quaternion rotation from an axis/angle representation, we need to know how a rotation quaternion is defined. This function is built into most quaternion implementations, but can be summarized as:

quat.x = axis.x * sin(angle/2) quat.y = axis.y * sin(angle/2) quat.z = axis.z * sin(angle/2) quat.w = cos(angle/2)

So we have both the case when the object is exactly in front of us or exactly behind us taken care of. What about when the object is somewhere in between?

We can use the vector cross product to obtain an axis of rotation that is perpendicular to our forward facing direction and the new direction. Imagine in the drawing below the axis found by the cross product going up and down into the screen and using it like a spindle to spin/rotate from A to B.

Using this axis and the angle we have obtained, we can get a final rotation that will put A’s forward axis facing the point at B.

// remember that the angle returned from // Vector3.Dot() is actually the cosine of the // desired angle. We use Acos to get the // true angle in radians angle = Math.Acos(angle); // this obtains the rotational axis in the drawing Vector3 axis = Vector3.Cross(forward, targetAtNewOrigin).Normalize(); // this is our final rotation to set on A to point its forward // axis to the object finalRotation = Quaternion.FromAxisAngle(axis, angle); A.rotation = finalRotation;

And now I’m lookin’ at you!

Your grammatical typing skills are far superior to your penmanship (especially in that last diagram ) *grins*. Okay, all kidding aside, sharing stuff like this is really important. I myself find it really fascinating to see what goes on behind the scenes. It’s easy to just log in to inworldz, do my thing and then complain when things go wrong, forgetting about all the background craziness that needs to happen to make it all work. Thanks for sharing.