Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice
  3. Join us on November 16th, 2023, between 1 pm and 9 pm CET for Ask the Experts Online on Discord and on Unity Discussions.
    Dismiss Notice

F'ing Quaternions -- How do they work?

Discussion in 'Scripting' started by Cybearg, Jul 13, 2016.

  1. Cybearg

    Cybearg

    Joined:
    Jan 18, 2015
    Posts:
    46
    I have a specific question, but I'm also sick of Quaternions being a pain in my ass every time I want to do anything in Unity, so I'll ask up front -- anyone know of any good lectures for dumb idiots like me that will explain Quaternions simply, yet extensively enough that from now on I'll "get it", rather than having to constantly Google for code snippers and half-answers that I don't understand? I'd really like to get over this frustrating learning curve.

    As for my specific question (which is for a 2D project), I have a script that (thanks to a lot of Googling and luck) will rotate the player to face in the direction of an AreaEffector and also rotate a player's feet toward the axis of an object with a Point Effector. I specifically want to expand this second part so that, if the force is positive (so the player is pushed away from the Point Effector rather than drawn into it), the player's feet will instead rotate outward to face away from the Point Effector game object's central axis. Basically, I want to take this method and get it to return a final Quaternion that is rotated an extra 180 degrees around the Z axis (compared to what the result is now, effectively rotating the player so the feet will be where the head is and vice versa).

    How do I do it? Please help, I hate Quaternions!
     
    Random_Deslime likes this.
  2. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,848
    How about this?



    As for your specific question, I'm afraid I didn't follow most of it, except this part:

    And that's easy enough. You've got your effectorRotation; if you want to append an extra 180° rotation around Z, then this would be simply

    Code (CSharp):
    1. effectorRotation * Quaternion.Euler(0, 0, 180)
    Or you can multiply in the other order, depending on whether you mean to do this extra rotation before or after what you're doing now (which is effectively the difference between rotating in the global Z axis vs. the local Z axis).

    For what it's worth, I love quaternions... they represent a rotation in a very concise, useful way, and can be composed just by multiplication. What more could you want from a method of representing rotations?

    Best,
    - Joe

    P.S. Please use the "Insert" button to put your code right in your post, rather than using Hastebin... it's much easier to read and reply to that way.
     
    Random_Deslime likes this.
  3. Cybearg

    Cybearg

    Joined:
    Jan 18, 2015
    Posts:
    46
    Thanks for the reply! I'll check out the video (actually saw a link to it but figured Numberphile wouldn't necessarily be the best introduction).

    I've actually found an answer similar to this by Googling, giving me this result:
    Code (CSharp):
    1.     private void GetRotationFromEffector() {
    2.         var gravityUp = (objectToRotate.transform.position - currentEffector.transform.position).normalized;
    3.         var localUp = objectToRotate.transform.up;
    4.  
    5.         effectorRotation = Quaternion.FromToRotation(localUp, gravityUp) * objectToRotate.transform.rotation;
    6.         effectorRotation *= Quaternion.Euler(0, 0, 180);
    7.     }
    ... But unfortunately it doesn't work. It DOES rotate the player so head faces inward and feet face outward, but then after a second or so, the player starts rotating on the X and Y axes, which means that the 2D player folds paper-thin. And this is why I hate Quaternions -- they don't do what you'd expect them to do. Why does making a rotation on the 180 z also cause unwanted rotation in the x and y, and how do I stop it?

    The player ends up with a rotation of 11, 270, and 180, when it SHOULD be 0, 0, 191 (it just so happens the player's angle should be 11 degrees in this case).

    Documentation that explains clearly how to use them. :( I've never once had a Quaternion work as expected. It's always offered some headache-inducing strange behavior that I have to search for an answer for.

    This is what I'm talking about. Literally everywhere else in math, x * y and y * x mean the same thing, except apparently with Quaternions x * y != y * x. That's confusing.
     
    Random_Deslime likes this.
  4. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,386
    So the more you work with Quaternions the more you realize that the implementation you see in Unity is some smart dude being nice and giving us the easy front end so we don't have to deal with the madness of actually computing Quaternions. At least, that has been my experience. From what I can tell, what we're seeing is the easy side of things. I had to screw with them a lot when making IK point in the right direction which turned out to be a bug, but made me try just about everything I could with them to make it work.

    I've burned most of the memory of them away with alcohol but I remember a few things, for instance x * y is adding two Quaternions together - not multiplication. You can actually learn a huge amount by carefully reading the Quaternion documentation itself which is what I did while also googling how they generally are supposed to work from places like stackexchange.

    What I ended up using most often was Quaternion.LookRotation which simply asks for forward and up, then returns a rotation for you. This is pretty flexible once you get used to it.

    All I can really say is read the documentation and test the example codes. They work, and they show you what things do so you can find what you need.
     
    Random_Deslime likes this.
  5. Cybearg

    Cybearg

    Joined:
    Jan 18, 2015
    Posts:
    46
    Thanks for the link. After a bit of looking, I've found that this:
    Code (CSharp):
    1. effectorRotation * Quaternion.Euler(0, 0, 180);
    is the same as this:
    Code (CSharp):
    1. effectorRotation * Quaternion.AngleAxis(180, Vector3.forward);
    which is the same as this:
    Code (CSharp):
    1. Quaternion.Euler(effectorRotation.eulerAngles.x, effectorRotation.eulerAngles.y, effectorRotation.eulerAngles.z + 180);
    But unfortunately they're all still wrong. Again, instead of my final rotation being 0, 0, 191, it ends up being 11, 270, 180. What's with that?

    Keep in mind by "final rotation", I'm referring to the fact that I'm using this to angle the player in FixedUpdate():
    Code (CSharp):
    1. objectToRotate.transform.rotation = Quaternion.Slerp(objectToRotate.transform.rotation, effectorRotation, rotateSpeed * Time.deltaTime);
     
    Random_Deslime likes this.
  6. jimroberts

    jimroberts

    Joined:
    Sep 4, 2014
    Posts:
    560
    This website has a lot of information regarding quaternions. You need to understand that there are multiple ways to represent the same rotation with a quaternion. However, the result of converting a quaternion back to euler angles should give you the correct results.. What is the value of "effectorRotation"?
     
  7. Cybearg

    Cybearg

    Joined:
    Jan 18, 2015
    Posts:
    46
    effectorRotation is a private class member that holds the rotation I'll be using to Slerp in FixedUpdate. I get it like so:
    Code (CSharp):
    1. var gravityUp = (objectToRotate.transform.position - currentEffector.transform.position).normalized;
    2. var localUp = objectToRotate.transform.up;
    3.  
    4. effectorRotation = Quaternion.FromToRotation(localUp, gravityUp) * objectToRotate.transform.rotation;
    Keep in mind I don't understand why this code works. It's just something I snatched from StackExchange.
     
    Random_Deslime likes this.
  8. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,386
    So you're basically saying your current code is putting the rotation like it should, but you're just having trouble getting it to rotate 180 degrees. Is that right? Break it out and make sure it gives the result you expect, work backwards by taking it apart to confirm it is actually working properly in smaller chunks.

    So if it is working up to that point, you just do * AngleAxis and give it the local up?
     
  9. Cybearg

    Cybearg

    Joined:
    Jan 18, 2015
    Posts:
    46
    I don't understand what you mean. "give it" the local up? As in multiply? Or use some other function? What are you saying?

    I literally have no idea what any of this code means, so I don't know what I can or can't just. :)
     
  10. LaneFox

    LaneFox

    Joined:
    Jun 29, 2011
    Posts:
    7,386
    AngleAxis will do a rotation based on 1) how much you want to rotate 2) an up direction. So you just give it the local up direction since you probably dont want it to be in world space (as in Vector3.up).
     
  11. jimroberts

    jimroberts

    Joined:
    Sep 4, 2014
    Posts:
    560
    Can you give us the debug value of effectorRotation before you add the 180 degrees?
    Code (csharp):
    1. Debug.Log(effectorRotation.eulerAngles);
     
  12. Cybearg

    Cybearg

    Joined:
    Jan 18, 2015
    Posts:
    46
    So you mean something like this?
    Code (CSharp):
    1. effectorRotation = effectorRotation * Quaternion.AngleAxis(180, localUp);
    Because that's apparently nowhere close. The character just wiggles all over the place and I have no clue what direction it's going for (it has extensive spin in X, Y, and Z, which is very, very not what I want).
     
  13. Cybearg

    Cybearg

    Joined:
    Jan 18, 2015
    Posts:
    46
    The result is:
    I want it to be:
     
  14. jimroberts

    jimroberts

    Joined:
    Sep 4, 2014
    Posts:
    560
    You're getting (11, 270, 180) with the following code?

    Code (csharp):
    1. Quaternion effectorRotation = Quaternion.Euler(0, 0, 11);
    2. effectorRotation *= Quaternion.Euler(0, 0, 180);
    3. Debug.Log(effectorRotation.eulerAngles);
     
  15. Cybearg

    Cybearg

    Joined:
    Jan 18, 2015
    Posts:
    46
    Well, the initial effectorRotation value isn't explicitly set like that -- it comes form the angle between the player and the PointEffector's game object. This is how it currently works:

    I want the player instead rotate so the feet are facing outward, rather than the head (191 degrees instead of 11 degrees on the z). Your code works fine, but like I said, my method works like this:
    Code (CSharp):
    1. var gravityUp = (objectToRotate.transform.position - currentEffector.transform.position).normalized;
    2. var localUp = objectToRotate.transform.up;
    3.  
    4. effectorRotation = Quaternion.FromToRotation(localUp, gravityUp) * objectToRotate.transform.rotation;
    If I do something like this instead:
    Code (CSharp):
    1. var gravityUp = (objectToRotate.transform.position - currentEffector.transform.position).normalized;
    2. var localUp = objectToRotate.transform.up;
    3.  
    4. effectorRotation = Quaternion.FromToRotation(localUp, gravityUp) * objectToRotate.transform.rotation;
    5. effectorRotation *= Quaternion.Euler(0, 0, 180);
    I get this:

    Which you can see almost works, but the player ends up flipping around in ways I don't want them to (you see this happen at about the 3-second mark).

    The full code for this script (without the Euler(0, 0, 180) part) is here.
     
    Last edited: Jul 14, 2016
  16. RockoDyne

    RockoDyne

    Joined:
    Apr 10, 2014
    Posts:
    2,234
    If I had to guess these are likely the problem. If either of those end up having values on more than two dimensions (and those still have to be the two dimensions you want), you're going to get rotation off of your intended axis.


    The closest I've come to understanding quaternions, as far as the values are concerned, is to visualize an arrow pointing to the surface of a sphere. That takes three dimensions because it's actually mapping to a point in space. The forth variable is then a handle on top of the arrow controlling it's roll, although the alternate way to look at it (more mathematically) is as a variable that inversely determines the size of the sphere since the magnitude of a quaternion is always 1 (for a rotation). For only one axis of rotation, quaternions are good about keeping the variables used down to two (essentially representing a unit circle), but the instance another axis comes into play, all of a quaternion's variables come into play.

    At least that's the best way I can think about it without my brain exploding from trying to grasp 4D spatial constructs that needs three imaginary numbers to work.
     
  17. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Munchy2007 and Cybearg like this.
  18. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,848
    @Cybearg, I think the first step is to admit that the problem isn't Quaternions — it's rotations. If you don't understand why rotating by A and then B is different from rotating by B and then A (which in code, is A*B vs. B*A), then you don't understand rotations, plain and simple.

    At this point you should grab a coffee cup or other odd-shaped object off your desk and start playing with it. Do a X-axis rotation followed by a Z-axis rotation, and see what you end up with. Then do it in the other order. Different result, right?

    And as you do these manual tests, notice that you have to think carefully about what loose terms like "X-axis" means. Do you mean relative to your desk (the world), or relative to the coffee cup (local)? In fact you should always define your rotations relative to the desk. But note that initially these are the same (define a "starting" position for your coffee cup, neatly aligned with the desk). So, whatever rotation you do first is effectively a local rotation as well.

    None of this has anything to do with Quaternions; it is the very nature of rotation itself. Take the time to really grok all this fully.

    Then, when you say things like
    ...it indicates that something else entirely is going on. Quaternions have nothing to do with time. You have some other script that is running, or the same script that is running multiple times, with some cumulative effect you haven't fully thought through. Your original question was about a single rotation (and you can always cast a problem that way, by keeping track of what rotation(s) you need and starting from no-rotation every frame).

    Finally, if your player is 2D, then you really don't need to be (and almost certainly should not be) worrying about 3D rotations at all. Define what plane you're working in, define your rotation angle as a single (float) number of degrees, and rotate by that. For example, if (as in a standard sprite setup) you want to only rotate in Z, then forget about quaternions and just set transform.eulerAngles to (0, 0, angle).
     
    steve-thud, Munchy2007 and Cybearg like this.
  19. Cybearg

    Cybearg

    Joined:
    Jan 18, 2015
    Posts:
    46
    Thanks so much for the help, everyone! All the different resources and explanations have helped a ton. I feel like I've gone through a crash course, but I feel much more comfortable now!

    I finally follow what you mean, now. :) Now I've changed my code to not depend on fiddling with Quaternions and instead simply use explicit Z Eulers, while at the same time I feel I understand Quaternions better, too. The desired functionality no works without any strange turning on axes I didn't want manipulated.

    However, I'm still a little unclear on how to manipulate Quaternions. Aside from using some convenient Quaternion functions, setting Eulers, and combining Quaternions (or slerping), are there any other common things done with Quaternions? Because while I feel like I follow what they are and have a jist of how they work and how to use them in the Euler/combining sense, I don't see how to interact with them beyond that. Is there still more to it, or do most people just add Eulers to Quaternions, multiply them against each other, Slerp, and use a handful of the built-in functions?
     
  20. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    As a general rule you don't manipulate quarternions directly.
     
    image28 likes this.
  21. JoeStrout

    JoeStrout

    Joined:
    Jan 14, 2011
    Posts:
    9,848
    Yep, that's what people do. Treat a Quaternion as simply a black box that, through details best left hidden, represents a rotation. There are only so many things you might want to do with a rotation — set it, slerp it, compose it with another one; that's about it!
     
    Cybearg and Kiwasi like this.