Nerd Herder

Dean Johnson blogs about life on the XNA platform and tools team

You Spin Me Round

You Spin Me Round

Rate This
  • Comments 9

Often when working with 3D objects like characters and space ships you will need to point one object at the other. In this post we will cover two ways you can point objects at each other. The first way creates a matrix to turn your object towards the other. The second covers a way to determine the direction your object needs to turn.

Before we start we will cover some vector operations or what they represent. So here are a couple of the operations we will be using.

The vector cross product of X and Y creates a third vector Z that is orthogonal to both of them. Here are two resources for further information.

http://mathworld.wolfram.com/CrossProduct.html

http://en.wikipedia.org/wiki/Cross_product

The vector dot product represents a couple of things. Geometrically it represents the projection of X onto the unit length vector of Y. So you get the length of the X vector in the Y direction. This will be very useful for us later.

http://mathworld.wolfram.com/DotProduct.html

http://en.wikipedia.org/wiki/Dot_product

In order to turn the character towards another object (in this case, a sphere), we first create a vector between the two objects. Because we are in a right handed coordinate system we will be using –Z as our forward direction. To get the new forward vector of our object we need to take our position and subtract the position of the object.

characterLocalMatrix.Forward = characterLocalMatrix.Translation 
- spherePosition;

Now we need to normalize the forward vector because we want our local matrix to be orthonormalized. If the object had been scaled this would be a good time to add the scale back to the object by multiplying the forward vector by the objects scale in the Z direction.

characterLocalMatrix.Forward = Vector3.Normalize(
characterLocalMatrix.Forward);

Next we need to create a new right vector for the object. To do this we are going to cross the new forward vector with the world up vector. We do this because we don’t yet know what the up vector is going to be so we will come back later and fix the up vector. Note that if your objects have abnormal orientations this will not work because it assumes that world up is close to your eventual up vector. The new right vector also needs to be normalized.

characterLocalMatrix.Right = Vector3.Cross(characterLocalMatrix.Forward, 
Vector3.Up); characterLocalMatrix.Right = Vector3.Normalize(characterLocalMatrix.Right);

Finally we need to create our up vector by crossing the new right and forward vectors.

characterLocalMatrix.Up = Vector3.Cross(characterLocalMatrix.Right, 
characterLocalMatrix.Forward); characterLocalMatrix.Up = Vector3.Normalize(characterLocalMatrix.Up);

Our characters local matrix is now ready to use. If you are using the BasicEffect your draw code will look something like this.

effect.World = characterLocalMatrix;
effect.View = viewMatrix;
effect.Projection = projectionMatrix;

Now that you know how to turn directly towards another object, what happens when you want to know what direction your AI should turn in order to face you? We need a way to determine if it is better to turn left or right. We will use the dot product to determine if we should turn left or right. Like the last example the first thing we need to do is to create a vector to the target. We also want the vector to be unit length so we also normalize the vector.

Vector3 turnAt = spherePosition - characterLocalMatrix.Translation;
turnAt.Normalize();

Next we calculate the dot product with our right vector. You might wonder why the right vector when we are trying to turn our forward vector. The reason is because of the behavior of the dot product. By using the right vector the result of the dot product with the turnAt vector will result in the projection of the turnAt vector in the right direction. This will get of the length of the turnAt in the right direction. If the result is positive it means we should turn left and if the result is negative we should turn right.

// Determine direction we need to turn using right vector
float dotResult = Vector3.Dot(turnAt, characterLocalMatrix.Right);
// Turn left
if (dotResult > 0.1f)
{
    characterLocalMatrix *= Matrix.CreateRotationY(turnSpeed * 
gameTime.ElapsedGameTime.Milliseconds); } // Turn right else if (dotResult < -0.1f) { characterLocalMatrix *= Matrix.CreateRotationY(-turnSpeed *
gameTime.ElapsedGameTime.Milliseconds); }

You will notice that we don’t check dotResult with 0. This is because the character will turn past the object and then turn the other direction. This cycle will go back and forth causing the character to shake. So this technique uses a dead zone. Another option if to turn directly at the target when you get in this zone.

Well that’s the end of my first post. While the information here was geared towards people new to 3D graphics I hope it helped everyone.

  • Nice article.

    Talking about rotations, is there a way to rotate an bounding box? As the AABB is axis-aligned to the object's axis, maybe changing the pivot could return the searched result ... avoiding the use of bounding spheres ...

    Cheers!

    P.S.: there is a typo in "characterLocalMatrix.Right = Vector3.Normalize(characterLocalMatrix.)"; it should read "characterLocalMatrix.Right = Vector3.Normalize(characterLocalMatrix.Right)".

  • Ultrahead,

    If you just try to rotate an AABB every frame you can cause the box to collapse and change into the wrong shape. There is a way that you can rotate an AABB to be correct for the model but you need to have the original AABB so that you can always do a single rotation and not multiple ones each frame. If I start to blog about collision detection I will make sure to talk about this.

  • "There is a way that you can rotate an AABB to be correct for the model ..."

    That is what I'm looking for, but I couldn't find a direct way in XNA -maybe I'm missing something ... :)

    "... but you need to have the original AABB so that you can always do a single rotation and not multiple ones each frame."

    I see.

    Thanks for the answer ... ;)

  • Rotating an AABB wouldn't result in an AABB, as it wouldn't be axis aligned any more! You'd end up with what's commonly called an OOBB, or object aligned bounding box. You'll need a new intersection routine to work with one of these.

    If you'd like to construct a new AABB from the rotated (or in any way linearly transformed) AABB, what you need to do is apply your matrix to all 8 corners of the box, and take the new min max values for each 3 axis and redefine a box.

    Clearly this can result in poorly fitting boxes (imagine a long thin box at a diagonal angle resulting in a huge outer box). OOBB intersections are quite intensive, especially with each other. This is why quite a lot of people still prefer to use spheres: any linear transformation results in the same shape, and is trivial to apply to just the center point.

    You can get a very very long way with just hierarchical spheres.

    Back on topic though: dejohn, rather than omitting the last step in the rotation to face target (and thus leaving yourself slightly off target), wouldn't it be more appropriate to truncate the last step?

    Another property of the dot product that you haven't mentioned is that the dot product of two normalised vectors is the cosine of the angle between them.

    So to turn one vector (let's say Forward) to align with another (let's say TargetForward) as per your scenario above, I would:

    * Take the cross product of the two vectors to determine the axis of the rotation direction I need

    * Take the ACos of the dot product of the normalised version of both vectors to determine how far I need to rotate

    * Clamp the angle by the maximum speed I'm allowing the vector to rotate.

    Or in rough code:

    void RotateToFace( Matrix transform, Vector3 position, float maxTurnSpeed )

    {

       Vector3 targetForward = Vector3.Normalize( position - transform.Translation );

       Vector3 axis = Vector3.Cross( targetForward, transform.Forward );

       float angle = (float) Math.Acos( Vector3.Dot( targetForward, transform.Forward ) );

       angle = MathHelper.Clamp( angle, -maxTurnSpeed, maxTurnSpeed );

       transform *= Matrix.CreateFromAxisAngle( axis, angle );

    }

    In your scenario you only care about rotating about the Y axis, keeping your little fellow upright. This can be applied as an optimisation to the above by setting the y component of targetForward to 0 before normalising it (thereby projecting it flat onto the xz ground plane), omitting the cross product all together, and using CreateRotationY instead.

  • Ultrahead,

    If you would like to know how to rotate the AABB check page 86 in this book.

    http://www.amazon.com/Real-Time-Collision-Detection-Interactive-Technology/dp/1558607323/sr=8-1/qid=1169077621/ref=pd_bbs_sr_1/104-6530092-8079950?ie=UTF8&s=books

    As GameyLittleHacker stated the results of using an AABB can become less fitting when the object in long in a diagonal. A bounding volume hierarchy would probably would good for you. That is also covered in the book.

  • Thanks both for the explanations and the link to the book (I'll add it to my wishlist).

    Like 2 weeks ago or so I was playing around with some code to modify the min and max values of the box -in a similar way as GameyLittleHacker suggested- only to find that there's little-to-none control over the shape of the new BB (say, something like what Sharky's has posted: http://sharky.bluecog.co.nz/?p=108).

    So I'm convinced that a bounding sphere hierarchy is easier to implement and performs better, in general.

    Thanks again and regards ...

  • OOB Intersection Test With XNA: http://amapplease.blogspot.com/2007/01/oob-intersection-test-using-xna-as-we.html

  • I'm new to 3D graphics programming, but I understand you can get into gimble lock when rotating matrices. It doesn't look like Quaternions, which appear to avoid the issue, have some of the methods available to matrices. I understand you can create a matrix from a quaternion and then apply your technique, but when it comes time to rotate the object, would you be able to rotate the quaternion after you do a Quaternion.CreateFromRotationMatrix?

  • Thank you so much for this. I've been searching for days for a down to earth explanation of this subject.... I literally got up and danced upon finding this. Going to study this in greater detail tommorow. Thanks again!

Page 1 of 1 (9 items)
Leave a Comment
  • Please add 5 and 3 and type the answer here:
  • Post
Translate This Page
Search
Archive