Xbox LIVE Indie Games
Sort Discussions: Previous Discussion Next Discussion
Page 1 of 1 (11 posts)

Spherical Coordinates - prevent rotating at poles?

Last post 9/5/2008 7:52 AM by Bedroom Studio Entertainment. 10 replies.
  • 7/21/2008 8:20 PM

    Spherical Coordinates - prevent rotating at poles?

    First, let me explain what I'm trying to do. I have a mesh that is facing down the -Z axis.  I want it to point toward the camera by rotating around a sphere.  You might be thinking, "That's easy! Just use a Billboard matrix!"  But here's the kicker: the mesh should move in "steps".  If the camera moves a certain angle left or right, the mesh then moves one step left or right.  The same applies for up and down.  The mesh stays stationary until this angle threshold is crossed by the camera.

    I first thought this could be solved through Spherical Coordinates, so I implemented my mesh rotation to use the angles calculated from the camera position.  The problem with this is once you cross the "poles" (north and south), the mesh rotates around 180 degrees.  I know why this is happening (even though its a little hard to explain).  The main problem is, I don't really know how to fix it.  I'm thinking about calculating a third angle that handles the actual orientation of the mesh.

    Am I going about this the wrong way?  I feel like I'm making this too complicated.  It shouldn't be that hard to move an object around a sphere, right?
  • 7/21/2008 11:29 PM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    GlitchEnzo:
    The problem with this is once you cross the "poles" (north and south), the mesh rotates around 180 degrees.

    It is common for a camera, when passing through the north & south poles, to roll  about it's viewing direction vector approx 180 degrees (depending on its setup) to keep the up vector pointing as upward as possible. Is it possible the camera is rolling & not the mesh rotating? I can see where it would give the appearance of the mesh rotating (especially if there is no background). If this is the case, let us know, there are ways to around it.

    if that's way off base, it might be a good idea to post your code so we can more fully understand what you are dealing with. you can post on a pastebin.
  • 7/22/2008 1:02 AM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    Good guess Steve, but unfortunately that's not it.

    As I stated before, I know why it's flipping 180 degees. I just thought it was too long of an explanation to put in my post.

    The "vertical" angle, theta, ranges from 0 to pi.  Where 0 is pointing down the -Y axis and pi is pointing up the +Y axis. The "horizontal" angle, phi, ranges from 0 to 2pi.  Where 0 is pointing along the +X axis, pi/2 is pointing along the +Z axis, pi is pointing along the -X axis, and 3pi/2 is pointing along the -Z axis.

    In the case when the camera passes over the north (+Y) pole, theta is at its peak (pi) but then starts decreasing as you continue to the other side.  Whereas phi essentially gets +pi added to it (or -pi depending up where the pole is crossed).  That is why the object is being rotated 180 degrees (pi radians).

    The up vector of the camera doesn't factor into this because theta and phi are calculated soley from the position of the camera.

    The code itself is rather simple.

    Some notes:
    The values are added to theta and phi because my mesh is pointed down the -Z instead of the +X or +Y.
    The axis for the vertical rotation is calculated by rotating the +X axis around by phi radians.

    Any other ideas on what may stop the rotation at the poles?

     


    //in Update() 
    theta = -Math.Acos(camera.Position.Y / camera.Position.Length()) + MathHelper.PiOver2; //vert 
    phi = -Math.Atan2(camera.Position.Z, camera.Position.X) + 3*MathHelper.PiOver2; //horz 
     
     
    //in Draw() 
    Matrix horzRotation = Matrix.CreateRotationY((float)phi); 
    effect.Parameters["World"].SetValue(horzRotation * Matrix.CreateFromAxisAngle(Vector3.Transform(Vector3.UnitX, horzRotation), (float)theta)); 
     
  • 7/22/2008 5:13 AM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    You have to control the rotation of the mesh in use of a Quaternion.

    A Quaternion is a 4D vector that holds the three rotations plus a additional one.

    Quaternion Quat = Quaternion.CreateFromYawPitchRoll(yaw, pitch, roll); //your starting angles

    And now if you want to rotate the mesh around a axis:

    Matrix m = Matrix.CreateFromQuaternion(Quat);

    m = m * Matrix.CreateRotationX(amount) * Matrix.CreateRotationY(amount); //or how you ever want to rotate it.

    Quat = Quaternion.CreateFromMatrix(m);

    This actually dose not solve your problem, but it bypass's it. ;)

     

    Greg the Mad

  • 7/22/2008 1:41 PM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    Glitch, don't you hate it when instead of answering the question, people give their idea of how everything should be changed . So let me apologize up front because I'm doing that to you now. So just throw this away if you want to. I may not have enough info about your game to know if this would even be viable for you. But in your first post you said " Am I going about this the wrong way?  I feel like I'm making this too complicated."; so I thought I'd post an approach that, in my mind, appears less complicated.

    Working with the actual angles is causing (or contributing to) your current polar hurdle. There may be a way here that you would not need to key you mesh rotations off the actual angles of camera location but rather off the direction vector between the camera & mesh. Then it wouldn't care whether it's in the polar region or anywhere else.

    It appears that your mesh stays in one spot & the camera rotates around it. You want the mesh to always rotate to face the camera. But you want that stepping action where the camera has to rotate a little before the mesh rotates to face it. If I were to do it, I'd explore something like this;

     

                // create in fields section outside loop 
                Matrix modelWorld;  
                float stepValue = 0.95f;//adjust stepValue to fit your incremental stepping rotation idea. 
                 
                // in update 
                Vector3 camModelDir = Vector3.Normalize(camera.Position - model.position); 
                if (Vector3.Dot(camModelDir, modelworld.forward) < stepValue)//if the angle between camera & model face gets big enough, do the stepping step... 
                { 
                    modelWorld = Matrix.Identity; 
                    modelWorld.Forward = camModelDir; 
                    if (Vector3.Dot(modelWorld.Forward, Vector3.Up) < 0.7071f && Vector3.Dot(modelWorld.Forward, Vector3.Up) > -0.7071f)//if camera is more horizontal in viewing direction than vertical... 
                    { 
                        modelWorld.Right = Vector3.Normalize(Vector3.Cross(modelWorld.Forward, Vector3.Up)); 
                        modelWorld.Up = Vector3.Cross(modelWorld.Right, modelWorld.Forward); 
                    } 
                    else //the point of splitting this up with the "IF...ELSE..." is to keep the dot product result away from 1 or -1 where small differences = large angle changes. things work smoother. 
                    { 
                        modelWorld.Up = Vector3.Normalize(Vector3.Cross(modelWorld.Forward, Vector3.Right)); 
                        modelWorld.Right = Vector3.Cross(modelWorld.Forward, modelWorld.Up); 
                    } 
                    modelWorld.Translation = model.position; // this line is not necessary if your model stays at (0,0,0)  
                } 
                effect.Parameters["World"].SetValue(modelWorld); 

     


     There is an assumption here that the modelWorld.Forward is the direction that that the mesh should have pointed to the camera. If not then I'd either change it in the 3d modeling app or set up a little correction rotation constant.

    I didn't test this code. It is intended not as a canned solution but a means to convey a possible alternative approach to your goal that would avoid your current polar hurdle and arguably be less complicated.

  • 7/22/2008 5:43 PM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    Thanks for putting so much thought into it Steve!  Don't worry about coming up with a different approach.  I was fully willing to accept any solution so long as it fixed my problem.

    I haven't had a chance to try out your code yet (that will have to wait until I get back home), but I think I understand the basic concept of how it works.  I really like how you use the normalized dot product in order to avoid having to calculate the arccosine.

    Unfortunately, I think I see a potential problem (if I'm understanding it correctly).  Let me present a hypothetical situation.  Let's say the stepping angle is 18 degrees and the camera is moved 20 degrees to the left and 9 degrees up.  This code will see that it exceeded 18 degrees, and then it will see that it was exceeded in the horizontal direction.  That's fine, but here is the problem: the modelWorld.Forward is then set to the cameraModelDir.  That means that in the next frame (if the camera doesn't move), the angle will be 0.  It order for the stepping to work correctly, it would need to know that the camera now lies 2 degrees to the left and 9 degrees up from the mesh.  I think this problem could be fixed by setting modelWorld.Forward to a rotated cameraModelDir.

    Am I understanding your implementation correctly?
  • 7/23/2008 6:33 AM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    Success!  I have a working solution! (Thanks a lot Steve!  Your guidance really helped me out.)

    I appreciate it greatly when other people post solutions once they get them, so I thought I would do the same.  Enjoy!  (I know its not optimized or cleaned up.  I wanted to write it in the most easy to understand manner. (updateAngle is the stepping angle)

     

    Vector3 dirToCam = Vector3.Normalize(camera.Position - Vector3.Zero); //should subtract object position, but that is always 0,0,0 
    double camAngle = AngleBetweenVectors(worldMatrix.Forward, dirToCam); 
    if (camAngle >= updateAngle) //the camera moved far enough that the mesh should be moved 
       double upAngle = AngleBetweenVectors(worldMatrix.Up, dirToCam); 
       if (upAngle <= MathHelper.PiOver2 - updateAngle) 
       { 
          //rotate Forward and Up upwards around Right 
          Matrix rotation = Matrix.CreateFromAxisAngle(worldMatrix.Right, (float)updateAngle); 
          worldMatrix.Forward = Vector3.Transform(worldMatrix.Forward, rotation); 
          worldMatrix.Up = Vector3.Transform(worldMatrix.Up, rotation); 
       } 
       else if (upAngle >= MathHelper.PiOver2 + updateAngle) 
       { 
          //rotate Forward and Up downwards around Right 
          Matrix rotation = Matrix.CreateFromAxisAngle(worldMatrix.Right, -(float)updateAngle); 
          worldMatrix.Forward = Vector3.Transform(worldMatrix.Forward, rotation); 
          worldMatrix.Up = Vector3.Transform(worldMatrix.Up, rotation); 
       } 
     
       double rightAngle = AngleBetweenVectors(worldMatrix.Right, dirToCam); 
       if (rightAngle <= MathHelper.PiOver2 - updateAngle) 
       { 
          //rotate Forward and Right leftward around Up 
          Matrix rotation = Matrix.CreateFromAxisAngle(worldMatrix.Up, -(float)updateAngle); 
          worldMatrix.Forward = Vector3.Transform(worldMatrix.Forward, rotation); 
          worldMatrix.Right = Vector3.Transform(worldMatrix.Right, rotation); 
       } 
       else if (rightAngle >= MathHelper.PiOver2 + updateAngle) 
       { 
          //rotate Forward and Right rightward around Up 
          Matrix rotation = Matrix.CreateFromAxisAngle(worldMatrix.Up, (float)updateAngle); 
          worldMatrix.Forward = Vector3.Transform(worldMatrix.Forward, rotation); 
          worldMatrix.Right = Vector3.Transform(worldMatrix.Right, rotation); 
        } 
     
    double AngleBetweenVectors(Vector3 vector1, Vector3 vector2) 
       return Math.Acos(Vector3.Dot(vector1, vector2) / (vector1.Length() * vector2.Length())); 

  • 7/23/2008 1:39 PM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    Glad you got it working. 

    There is one thing I'll mention. Not because it's causing you any problems right now but more from a best practices standpoint and you are definitely able to understand it.

    You are storing the mesh's orientation in worldMatrix and persisting that stored information frame to frame while modifying it as it encounters your IF...ELSE IF... statements.

    Every time one of the vectors is acted upon through the Vector3.Transform(), the result is very slightly inaccurate because of floating point rounding errors. Very minor errors. But over the course of many, they potentially can accumulate.

    So although the Forward & the Right may have been rotated by the same rotation matrix, there could be very minor differences in where they end up versus where they started. All this to say that over time, worldMatrix has the potential to become skewed, even though your code does everything correctly.

     
    The Forward, Right, & Up vectors are all supposed to be 90 degrees to each other and of unit length (normalized) and that's what is meant by an orthonormal matrix. So I don't think there's anything in your code yet to cancel those errors by re-aligning the vectors to be 90 degrees or to keep those vectors at unit length. Commonly it's done by normalizing the vectors after manipulating them & crossing the vectors to orthogonalize (not really a word) them.

     here is a possible way to do that in your situation;

    Since the only time the matrix is modified is when if (camAngle >= updateAngle) is true, you could implement this after modifying the vectors, but before leaving the block.

     

    Vector3.Normalize(worldMatrix.Forward); // normalizes that vector. V.Forward is the key vector. the other two will be re-aligned (90 deg) to it 
    worldMatrix.Right = Vector3.Normalize(Vector3.Cross(worldMatrix.Forward, worldMatrix.Up)); // re-align the V.Right to be 90 degrees to V.Forward and normalize it. 
    worldMatrix.Up = Vector3.Cross(worldMatrix.Right, worldMatrix.Forward); // re-align V.Up to be 90 deg to the other two. 
                                                                            //Since it is created from two normalized vectors that are orthogonal,  
                                                                            //it will be made normal length. if either the others were not normal length or if they wern't ortho,  
                                                                            //it would not be made normal length. so it's ok to skip the normalizing on this one but don't let it bite you someday. 
     
     


     Although this looks like it makes the code heavier, it can also lead to optimizations. For instance, since the Up vector is created in the end by crossing the other two, did you really have to rotate it in the IF..ELSE IF.. part? same for the Right, you may (every situ is different) have been able to make a temporary Right by crossing Forward with Vector3.Up, then use that temp to make your actual Up from a cross, then going back and making a permanent Right by crossing your Forward & Up (that's what I was planning with the code I made in the previous post  but I forgot to go back & make a perm... oops). Then the only Vector3.Transform you have above would be the Forward. Crossing & dotProducting are usually faster than vector3.Transforming & working with actual angles. and since you need to assure an orthonormal matrix anyways... might as well use 'em.

  • 7/23/2008 4:07 PM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    You bring up very good points.  As I was writing the code, I thought about using the cross product to calculate the up and right vectors.  I just wrote it as a vector transformation because I thought it was easier to understand and to debug.  Now that I have a working solution, I can go through and optimize it and make it more accurate.

    I will add the normalization and cross product code to the end like you suggested.  I didn't even think of the floating point error that would slowly accumulate. Good catch!

    I also really want to get rid of the 3 arccosines by switching to your method of comparing the angles (normalized dot product compared to the cosine of the angle [calculated beforehand]).

    Thanks for all of your help!

  • 7/23/2008 5:08 PM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    BTW, I was impressed with the (horzRotation * Matrix.CreateFromAxisAngle(Vector3.Transform(Vector3.UnitX, horzRotation), (float)theta));    That's quite a line to wrap one's head around. Remind me never to play chess against you. ;-)

     


     
  • 9/5/2008 7:52 AM In reply to

    Re: Spherical Coordinates - prevent rotating at poles?

    Hi,

    I have bypass the problem adding quatertion vector to quaternion calculated at previously step:

    _quaternion *= Quaternion.CreateFromAxisAngle(new Vector3(0, 0, 1), MathHelper.ToRadians(angle_alfa));  
    _quaternion *= Quaternion.CreateFromAxisAngle(new Vector3(1, 0, 0), MathHelper.ToRadians(angle_beta));  
     

    finally, I can set position of my object:

    _position = Vector3.Transform(new Vector3(0, ray_orbit, 0), Matrix.CreateFromQuaternion(_quaternion)); 

    I hope this will help you solve your problem.

    Bye,

        Angelo

Page 1 of 1 (11 posts) Previous Discussion Next Discussion