Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

How to follow object/rotate camera around X axis without it flipping?

Discussion in 'Scripting' started by unityrookie, Oct 7, 2010.

  1. unityrookie

    unityrookie

    Joined:
    Sep 24, 2009
    Posts:
    34
    I'm really hoping someone can help me with this. My basic problem is that I have a camera following a ship. The ship can rotate 360 degrees around the X and Y axis. I was using the default SmoothFollow script for the camera, which works great for rotation around the Y axis, but as designed does not rotate around X. So, I tried to adjust it to work similarly around the X axis. However, what happens is once the ship rotates past 90 degrees up or down, the camera following it automatically flips around and everything is messed up.

    View a demo of the problem here: http://orgoquest.com/unitytest/rotationtest.html

    (Rotate around using the arrow keys, and wasd. Press hold down the up or down arrows and you'll easily see the problem)

    I'm sure this has to do with quaternions, which I still don't comprehend well, since I see in the inspector the camera rotation starts changing in all 3 planes all of a sudden. Here is the adjusted code I'm using:

    Code (csharp):
    1. var target : Transform;
    2. var distance = 10.0;
    3.  
    4. var height = 5.0;
    5.  
    6. var heightDamping = 2.0;
    7. var rotationDamping = 3.0;
    8.  
    9. private var wantedRotationAngleY:float;
    10.  
    11. // Place the script in the Camera-Control group in the component menu
    12. @script AddComponentMenu("Camera-Control/Smooth Follow")
    13.  
    14.  
    15. function LateUpdate () {
    16. // Early out if we don't have a target
    17. if (!target)
    18.     return;
    19.  
    20. // Calculate the current rotation angles
    21. wantedRotationAngleY = target.eulerAngles.y;
    22. wantedRotationAngleX = target.eulerAngles.x;
    23. wantedRotationAngleZ = target.eulerAngles.z;
    24.  
    25. //wantedHeight = target.position.y + height;
    26.  
    27. currentRotationAngleY = transform.eulerAngles.y;
    28.  
    29. currentRotationAngleX = transform.eulerAngles.x;
    30.  
    31. currentRotationAngleZ = transform.eulerAngles.z;
    32.  
    33. //currentHeight = transform.position.y;
    34.  
    35. // Damp the rotation
    36.  
    37. currentRotationAngleY = Mathf.LerpAngle (currentRotationAngleY, wantedRotationAngleY, rotationDamping * Time.deltaTime);
    38. currentRotationAngleX = Mathf.LerpAngle (currentRotationAngleX, wantedRotationAngleX, rotationDamping * Time.deltaTime);
    39. currentRotationAngleZ = Mathf.LerpAngle (currentRotationAngleZ, wantedRotationAngleZ, rotationDamping * Time.deltaTime);
    40.  
    41.  
    42. // Convert the angle into a rotation
    43. currentRotation = Quaternion.Euler (currentRotationAngleX, currentRotationAngleY, currentRotationAngleZ);
    44.  
    45. // distance meters behind the target
    46. transform.position = target.position;
    47. transform.position -= currentRotation * Vector3.forward * distance;
    48.  
    49. // Always look at the target
    50. transform.LookAt (target);
    51. }

    Thanks in advance for any help.
     
  2. Nikolay116

    Nikolay116

    Joined:
    Mar 21, 2010
    Posts:
    421
    I think you have gimble lock problem because you are using Euler Angles. There is no way you can avoid it unless you switch to quaternions for rotation.
     
  3. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Just to prevent any confusion here, whether or not you encounter gimbal lock actually doesn't have anything to do with whether you use quaternions or not; you can get gimbal lock with quaternions, and you can avoid gimbal lock without them.

    @The OP: That said, it is true that if you want to avoid gimbal lock or other artifacts associated with Euler angles, you'll probably want to work with the 'rotation' and/or 'localRotation' fields rather than the Euler angle fields.
     
  4. Vicenti

    Vicenti

    Joined:
    Feb 10, 2010
    Posts:
    664
    All the material I've found states that quaternions are explicitly designed to be incapable of entering gimbal lock... due to their operating on all three axes at once, rather than one at a time.
     
  5. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    I believe it. However, it's still wrong.

    As I wrote recently on another forum in response to a similar post, the idea that quaternions are 'the' solution to gimbal lock or that they are not susceptible to gimbal lock is probably the single most widespread misconception in the game development community (IMX, at least). I'm quite sure you've read many times that quaternions are the 'fix' for gimbal lock, but that doesn't indicate that the idea is correct; rather, it simply demonstrates how pervasive the misconception is.

    I don't know where the idea got started exactly, but once it found its way into a few commonly referenced tutorials, it sort of took on a life of its own, to the point where people tend to repeat it as fact without really giving it any critical thought.

    To consider your statement, first of all, as far as I know, quaternions weren't 'designed' with gimbal lock in mind (not based on my understanding of their history, at least). Secondly, quaternions do not 'operate on all three axes at once' any more or less than do rotation matrices. In the given context, a quaternion encodes an axis-angle rotation, just like a rotation matrix does. There's no 'all at once' or 'one at a time' about it; it's just an axis-angle rotation.

    In short, quaternions have the same fundamental behavior with respect to rotations as matrices do; the differences between the two have to do with efficiency, storage requirements, and other technical issues, not fundamental behaviors. You can encounter gimbal lock with matrices, and you can ensure that gimbal lock will not occur with matrices; you can encounter gimbal lock with quaternions, and you can ensure that gimbal lock will not occur with quaternions. The issues of whether quaternions are used and whether gimbal lock is a possibility are completely orthogonal; they have nothing to do with each other.

    Gimbal lock is the result of certain rotations being applied in sequence. The rotations themselves can be represented using quaternions, matrices, or whatever, but if the rotation is constructed using sequential rotations in the wrong way, you'll encounter gimbal lock. The solution to gimbal lock is not to swap matrices for quaternions or vice versa, but rather to construct and modify your rotations in a different way.
     
    Last edited: Oct 8, 2010
  6. Nikolay116

    Nikolay116

    Joined:
    Mar 21, 2010
    Posts:
    421
    I am afraid this is wiki kinda info. Open any book on classical mechanics. Quaternion as well as orthogonal matrices as well as all kind of angles are mapping the orientation of a body onto a unit sphere. The difference is that the quaternion mapping is single valued, while angles and matrices can run into problem when a subset of values correspond to the same orientation or vica-versa. This is in most cases gimble lock. For Euler angles it is when two angles sum to 180, than the other one is undetermined.


    They are doing the same thing, right, orientation handling. But I would not call it the same fundamental behavior. You are basicall saying that two functions, a continues one and a discontinues one, are the same if they take the same values .
    .


    I would like to see an example of running into gimble lock problem when you are using only quaternions for keeping object rotation and appling new ones

    This is partly true. If somebody like the author is keeping the orientation in quaterions (via unity), but then converts them into euler angles, does the operations he needs, then goes back to quaternions, then indeed there is no guaranty he won't run into gimble lock.
     
    Last edited: Oct 8, 2010
  7. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    To begin with, I can absolutely assure you that matrices are no more prone to gimbal lock than are quaternions. If you think otherwise, then you're misunderstanding the underlying mathematics.

    Not sure what you mean by that.

    No, you've got it wrong. Can you provide an example of a case where you'd run into gimbal lock with matrices but not with quaternions?

    Also, note that the quaternion representation is actually a double cover (q and -q represent the same rotation).

    The point is not that you can't avoid gimbal lock with quaternions (which you can); rather, the point is that you can avoid gimbal lock just as easily when using matrices.

    It's completely true. Switching from matrices to quaternions or vice versa will not by itself fix the gimbal lock problem. To avoid gimbal lock, you must construct and manipulate your rotations in ways that aren't subject to gimbal lock. (Whether you use matrices or quaternions for this doesn't matter.)
     
  8. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    I'm going to expand on this a little in the hopes of clearing up some of the confusion here. First, regarding this statement:

    Just to be clear, if we set aside for the moment the fact that we're working with Unity specifically, this is absolutely, 100% false. In fact, you don't even need matrices to avoid gimbal lock; you can implement full, gimbal-lock-free 6DOF motion using nothing but simple vector math if you want.

    (Of course since we are using Unity, working with quaternions, either directly or indirectly, is probably what's necessary here.)

    The basic operation we're concerned with here is rotating a vector about another (unit-length) vector by some angle. An equation for this operation can be found here.

    If you expand this equation by hand, you end up with something like this:

    Code (csharp):
    1. x' = ax + by + cz
    2. y' = dx + ey + fz
    3. z' = gx + hy + iz
    Which can be expressed as a matrix-vector multiplication. Thus we see that an axis-angle rotation can be encoded in a matrix and used to rotate a vector.

    A similar result is possible with quaternions (this is all well-known of course, but I'm just trying to establish some context). Assuming standard quaternion multiplication order, the expression:

    Code (csharp):
    1. v' = q * v * conjugate(q)
    Where q is a unit-length quaternion, can be used to rotate a vector v (where v has been encoded as a quaternion for the purpose of the operation).

    If you expand *this* expression by hand, you get something similar to the expansion shown earlier, but with the coefficients in terms of the quaternion elements (x, y, z, and w). It can be shown that for a given axis and angle, these coefficients have the same values as those of the matrix form. In other words, it's exactly the same operation, just expressed differently.

    It can also be shown that applying the quaternion rotation formula using the quaternion product qp has the effect of first rotating the vector by p, and then by q:

    Code (csharp):
    1. (q * p) * v * conjugate(q * p) =
    2. (q * p) * v * (conjugate(p) * conjugate(q)) =
    3. q * (p * v * conjugate(p)) * conjugate(q)
    And thus we see that we can combine rotations in quaternion form just as with matrices.

    There's no magic here - we're just applying axis-angle rotations using different methods, that's all. Neither method (i.e. using matrices or quaternions) is any more or less prone to gimbal lock than the other. Whether we risk encountering gimbal lock is not a function of which representation is used, but rather of how the rotations are combined.

    To those claiming matrices behave differently than quaternions with respect to gimbal lock, here's a request. Post a single example - just a little code snippet will do - that shows a situation in which using matrices would introduce gimbal lock, but using quaternions wouldn't. If the claim that matrices are susceptible to gimbal lock and quaternions are not is true, posting such an example should be easy to do.
     
  9. windexglow

    windexglow

    Joined:
    Jun 18, 2010
    Posts:
    378
  10. unityrookie

    unityrookie

    Joined:
    Sep 24, 2009
    Posts:
    34
    I appreciate all the help and the discussion, especially since my knowledge of quaternions and gimbal lock is very limited and this helps with some of the confusion, as did this youtube tutorial http://www.youtube.com/watch?v=rrUCBOlJdt4.

    I ended up coming up with a solution that seems to work, which is basically what both Nikolay and Jesse recommended (don't swap between quaternion and euler). Here's the code in case anyone else runs into a similar problem:

    Windexglow, thanks for the link. I tried to download your demo, but it needs the additional files for the exe to work.

    Code (csharp):
    1.  
    2. // The target we are following
    3. var target : Transform;
    4. // The distance in the x-z plane to the target
    5. var distance = 2.0;
    6. // the height we want the camera to be above the target
    7. var height = 5.0;
    8. // How much we
    9. var heightDamping = 2.0;
    10. var rotationDamping = 3.0;
    11.  
    12. private var oldRotation:Quaternion;
    13. private var targetRotation:Quaternion;
    14.  
    15. function Start () {
    16.     oldRotation = target.rotation;
    17. }
    18.  
    19.  
    20. function LateUpdate () {
    21.     // Early out if we don't have a target
    22.     if (!target)
    23.         return;
    24.    
    25.     targetRotation = target.rotation;
    26.     currentRotation = Quaternion.Lerp (oldRotation, targetRotation, rotationDamping * Time.deltaTime);
    27.     oldRotation = currentRotation;
    28.     transform.position = target.position;
    29.     transform.position -= currentRotation * Vector3.forward * distance;
    30.     transform.LookAt(target, target.TransformDirection(Vector3.up));
    31.  
    32. }
     
  11. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    I'm glad the discussion was useful, but don't be thrown off by some of the confusing statements that have been made in the thread.

    It's kind of curious how determinedly people seem to cling to this particular misconception, but the idea that matrices and quaternions have different properties with respect to gimbal lock is just that - a misconception. (And you can take my word for that.)

    Whatever you might hear though, what I'd generally suggest is to learn the underlying math well enough that you can make these sorts of determinations for yourself, rather than relying on what you might read on the internet.

    The question you can ask yourself is, why would a rotation matrix be any more susceptible to gimbal lock than a rotation in quaternion form? See if you can come up with an example where the exact same process would lead to gimbal lock when done using matrices, but not when done using quaternions. (The lack of such examples from the other posters in this thread should be telling.)

    Now, that's mostly in reference to some of the comments that were made subsequent to your post (the quaternion-vs.-matrix issue isn't particularly relevant as far as your original post is concerned). But, I always try to address this issue when it comes up, just to try to counter this misconception where possible.

    Cool demo, by the way :)
     
  12. LakePlaceProductionsLLC

    LakePlaceProductionsLLC

    Joined:
    Jul 4, 2007
    Posts:
    36
    Hello, do you think it is possible to make an object like a golf club swing by swinging the iphone, using this approach? I'd be interested in focusing in on this type of solution...
     
  13. Naoki Zhen

    Naoki Zhen

    Joined:
    Dec 3, 2010
    Posts:
    4
    I'm dealing with the same problem. Rotating my spaceship around the X axis results in a camera flip at 90 degrees up or down. I was using the integrated "smoothfollow.js", after having read this thread I copied the recently posted code from unityrooky and tried it out.
    It works pretty well if I only rotate the ship along the X axis. The camera follows the spaceship and there's no flip at the top or bottom zenith. BUT if I combine the rotation of the X axis with the Y axis the ship doesn't move correctly, it commutes from left to right.

    This is my tiny source code to control the ship rotation:


    Code (csharp):
    1. using UnityEngine;
    2. using System.Collections;
    3.  
    4. public class FlightNavigation : MonoBehaviour {
    5.    
    6.     private float directionAngle, pitchAngle;  
    7.    
    8.     void Start () {
    9.     }
    10.    
    11.     void Update () {   
    12.        
    13.         directionAngle += .025f*(Input.mousePosition.x - Screen.width/2.0f);
    14.         pitchAngle += .05f*(Input.mousePosition.y - Screen.height/2.0f);
    15.                
    16.         transform.localRotation = Quaternion.identity;
    17.         gameObject.transform.RotateAround(Vector3.right, -.002f*pitchAngle);               
    18.         gameObject.transform.RotateAround(Vector3.up, .001f*directionAngle);
    19.     }
    20. }

    I uploaded the scene so you can test it. Just move the mouse and the ships orientation will follow. Just try to rotate a little while and you will see that the side controls for the Y axis mess up.
    Maybe anyone has an idea what is still wrong or what I can do to avoid this commuting effect. Or maybe there's a tutorial on how to correctly create a spaceship control with three freely movable axes.

    Spaceship Test:
    http://www.lomax-industries.com/unity_uploads/flight_test/WebPlayer.html

    Thanks in advance!!
    Best regards
     
  14. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Wow, haven't seen this thread in a while. Too bad there's so much wrong information in it :/

    It's a little hard to tell what's going on without knowing exactly what the camera's doing (since in some cases the motion of the camera can be hard to distinguish from the motion of the ship, for me at least).

    That said, I see at least a few potential problems with the method you're using. Before enumerating the problems though, I think it'd be useful to know exactly what behavior you're after. Are you looking for 6DOF motion, where all motion is relative to the local axes at all times? Or do you want the ship to remain more or less 'upright' relative to some reference plane? (That is, there should never be any roll relative to the original orientation.)
     
  15. Naoki Zhen

    Naoki Zhen

    Joined:
    Dec 3, 2010
    Posts:
    4
    Hi Jesse,

    thanks for your reply. Having done some inquiry I can say "Yes", 6DOF is exactly what I want to do.
    Honestly I'm not a pro when it comes to math, not at all. That's why I still don't really understand the problem of gimbal lock, but I have kinda suggestion in mind.

    Actually the camera isn't doing anything else than just what the following code allows to do:

    Code (csharp):
    1. // The target we are following
    2. var target : Transform;
    3. // The distance in the x-z plane to the target
    4. var distance = 7.0;
    5. // the height we want the camera to be above the target
    6. var height = 5.0;
    7. // How much we
    8. var heightDamping = 2.0;
    9. var rotationDamping = 3.0;
    10.  
    11. private var oldRotation:Quaternion;
    12. private var targetRotation:Quaternion;
    13.  
    14. function Start () {
    15.     oldRotation = target.rotation;
    16. }
    17.  
    18. function LateUpdate () {
    19.     targetRotation = target.rotation;
    20.     currentRotation = Quaternion.Lerp (oldRotation, targetRotation, rotationDamping * Time.deltaTime);
    21.     oldRotation = currentRotation;
    22.     transform.position = target.position;
    23.     transform.position -= currentRotation * Vector3.forward * distance;
    24.     transform.LookAt(target, target.TransformDirection(Vector3.up));
    25.  
    26. }
    As I said I'm using the code posted by unityrookie.

    Just to get a clear thought let me sum up my intentions. I want a ship-following camera keeping its "up-orientation" in the same way as the ship and just a standard(?) flight control mechanism of a spaceship in a zero-gravity world. Hope this wasn't to abstruse. ;)

    Thanks again for any help.
    Best regards
     
  16. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    I don't know how the 'smooth follow' script you're using works, but in order to correctly follow an object with full 6DOF motion, it'll have to be implemented appropriately. (If you implement 6DOF motion and then find the camera doesn't behave correctly, that'll be the next problem you'll want to solve.)

    As for the 6DOF motion, here's an (untested) modification of your code:

    Code (csharp):
    1. using UnityEngine;
    2.  
    3. public class FlightNavigation : MonoBehaviour
    4. {
    5.     private float rotationSpeed;
    6.  
    7.     void Update()
    8.     {
    9.         // NOTE: You might need to negate dx and/or dy here.
    10.         float dx = Input.mousePosition.x - Screen.width / 2.0f;
    11.         float dy = Input.mousePosition.y - Screen.height / 2.0f;
    12.        
    13.         transform.Rotate(Vector3.right, dy * rotationSpeed);
    14.         transform.Rotate(Vector3.up, dx * rotationSpeed);
    15.     }
    16. }
    This simply rotates the object about its local side and up vectors each update based on the current mouse position. One possible issue here is that the speed of rotation could be resolution-dependent (because the magnitude of dx and dy will be dependent on the screen dimensions). If that's an issue, you may want to use normalized mouse coordinates instead.
     
  17. sigbuserror

    sigbuserror

    Joined:
    Dec 4, 2010
    Posts:
    5
    I am having a similar issue. I have a ship which I want to follow as it flying around in the sky, doing loops etc. Using UnityRookies new camera it seems to follow better when flipping, but now it seems to not have as much give. Also I would be interested how people are implementing the 6DOF movement. Using basic rotations and translations on the player is generally working, but should I be using the character controller move command? Or forces on a rigid body? There seem to be many options and I am not sure which is most effective.
     
  18. Jesse Anders

    Jesse Anders

    Joined:
    Apr 5, 2008
    Posts:
    2,857
    Can't comment on the camera issue, but as for how to implement movement, that's basically up to you. I've implemented 6DOF motion using both applied forces and torques and direct manipulation of the transform, and both work fine (I haven't used a character controller for this, but I don't see why you couldn't). In short, you just have to decide how you want the object to behave and how you want the control scheme to work, and then choose an implementation method accordingly.