Search Unity

Weird Rotation Behaviour with Quaternion.LookRotation

Discussion in 'Scripting' started by Solid_Metal, Nov 16, 2017.

  1. Solid_Metal

    Solid_Metal

    Joined:
    Mar 25, 2015
    Posts:
    45
    hello good people of unity forum,

    i had this problem with a script that i made, i want to made a basic on-rail movement for object, it succeed and move flawlessly, but the rotation a bit off

    and heres the ilustration of what i want it to do





    so i have multiple waypoints (red circle) that form a circle, the object will move through all this waypoints while the rotation keep facing at the current waypoints position (blue arrow)

    heres the movement script
    Code (CSharp):
    1. distance = Vector3.Distance(PathToFollow.path_objs[leftWaypointID].position, transform.position);
    2. transform.position = Vector3.MoveTowards(transform.position, PathToFollow.path_objs[leftWaypointID].position, Time.deltaTime * speed);
    it moves like i expected, but the rotation a bit off, when it arrive at a point where the object will rotate more than 90 degrees, the object suddenly flip, and the one who facing the waypoints no longer the blue arrow but the red arrow

    .

    and here is the rotation code
    Code (CSharp):
    1. rotation = Quaternion.LookRotation(PathToFollow.path_objs[currentWaypointID].position - transform.position);
    2. transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * rotationSpeed);
    3. transform.rotation = Quaternion.Euler(new Vector3(0f, 0, transform.eulerAngles.z));
    this problem bugging me for a bout a week now, and i can't as of yet find any solution, if anyone can help i would be very grateful

    thank you in advance
     
  2. iamvideep

    iamvideep

    Joined:
    Oct 27, 2017
    Posts:
    118
    Why are you using the last ling where you set the Z axis?
     
  3. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Instead of Quaternion.LookRotation(...) it's probably worth using Transform.LookAt(...). This takes an optional second parameter for an "up" direction, which will remove the need for your third line.

    I suspect that the issue is that in some cases your X or Y rotation is going to be -180 after converting to eulers*, which you're then forcing back to 0, which flips the camera around regardless of what the Z angle is. It's typically not a great idea to mix quaternions and eulers, instead try to find the appropriate math to do the whole thing using just one or the other.

    * This is because there are multiple ways to represent equivalent rotations using eulers, and the computer has no way of knowing which one is more intuitive for whatever you're doing at the time.
     
    Solid_Metal likes this.
  4. Solid_Metal

    Solid_Metal

    Joined:
    Mar 25, 2015
    Posts:
    45
    because i do indeed only want it to move by Z axis , which afaik its the only way to do, the slerp somehow wont do it


    thanks for the clear and awesome reply, i'll try it ASAP
     
  5. Solid_Metal

    Solid_Metal

    Joined:
    Mar 25, 2015
    Posts:
    45
    unfortunately it didnt work, theres 3 problems i see using it, heres the snippet of the code
    Code (CSharp):
    1. transform.LookAt(PathToFollow.path_objs[currentWaypointID].position, Vector3.up);
    1. for some odd reason, even if i use the vector3.up, it still rotate all axis, including the Y that again for some reason it rotate to 90 and -90

    2. it still flip when it achieve the same point as the previous rotate code

    3. the transition not smooth

    or am i misused it?

    thank you in advance

    EDIT :
    and also i think its not because of the Euler, i change the code a bit by reference i got in google, heres the snippet :
    Code (CSharp):
    1.   rotation = Quaternion.LookRotation(PathToFollow.path_objs[currentWaypointID].position - transform.position);
    2. rotation.x = 0;
    3. rotation.y = 0;
    4. transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * rotationSpeed);
    it still flip on the same location
     
    Last edited: Nov 16, 2017
  6. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    1,065
    Quaternions are not vectors. What the values of a Quaternion do is not as obvious as a vector's, and should not be directly manipulated if one is not familiar with Quaternion math. I too am not familiar with it and choose to avoid it.

    I'm guesssing "PathToFollow.path_objs[leftWaypointID]" is the waypoint the character is supposed to move to.

    Code (CSharp):
    1. //get looking rotation in the form of a vector
    2. Vector3 lookVect = PathToFollow.path_objs[leftWaypointID].position - transform.position
    3. //remove height difference
    4. lookVect.y = 0;
    5. //derive look direction as a quaternion
    6. Quaternion targetRota = Quaternion.LookRotation(lookVect, Vector3.up);
    7. //apply rotation
    8. transform.rotation = targetRota;
    however this will still snap the rotation, so you will have to use Quaternion.RotateTowards to make it turn gradually at a constant speed.
     
    Last edited: Nov 16, 2017
    Solid_Metal likes this.
  7. Solid_Metal

    Solid_Metal

    Joined:
    Mar 25, 2015
    Posts:
    45
    agreed, i also barely understand it, but so far theres no better solution for it
    and i also try your code and modify it accordingly, unfortunately it didnt work, it wont face the next/previous (depending on the direction it move) , i already change the vector direction still no avail

    so again, i google, and found a bit of solution from a plugin called iTween, which i'm not use because of the limitation of the controllable object on-rail system, thus why i made it myself

    heres the snippet of the code that works quite well so far

    Code (CSharp):
    1.   Vector3 dir = PathToFollow.path_objs[currentWaypointID].position - transform.position;
    2.             Vector3 up = Vector3.Cross(Vector3.forward, dir);
    3.             Quaternion newRotation = Quaternion.LookRotation(dir, up);
    4.             newRotation.x = 0;
    5.             newRotation.y = 0;
    6.             transform.rotation = Quaternion.Slerp(transform.rotation, newRotation, Time.deltaTime * rotationSpeed);
    it doesnt flip anymore while on up side of the circle, but for some odd reason, it still flipped when i move it to the left, like it get negative or minus for some reason i'm not exactly sure what happen
    but going to the right works great
     
    Last edited: Nov 16, 2017
  8. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    1,065
    Well the code snipplet I provided earlier is working fine for me for the exact same purpose of facing the waypoint my character is going towards, so the problem might lie elsewhere.
     
  9. Solid_Metal

    Solid_Metal

    Joined:
    Mar 25, 2015
    Posts:
    45
    probably, but i do tinker with your code again, which work but with the same problem i got from my first code, which is on certain point , it'll flip


    Code (CSharp):
    1.  //get looking rotation in the form of a vector
    2.             Vector3 lookVect = PathToFollow.path_objs[currentWaypointID].position - transform.position;
    3.             //derive look direction as a quaternion
    4.             Quaternion targetRota = Quaternion.LookRotation(lookVect, Vector3.up);
    5.             targetRota.x = 0;
    6.             targetRota.y = 0;
    7.             //apply rotation
    8.             transform.rotation = targetRota;
     
  10. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    1,065
    I don't understand the purpose of these lines:
    Code (CSharp):
    1. targetRota.x = 0;
    2. targetRota.y = 0;
    targetRota is a quaternion, you should not be touching its values directly.
     
  11. Solid_Metal

    Solid_Metal

    Joined:
    Mar 25, 2015
    Posts:
    45
    it meant to lock the rotation only rotate on certain axis, which in my case is Z, if i'm not use it, all of the axis regardless used the vector3 direction, still will rotate on all axis

    and also i think i made a mistake, i think the correct one is use the forward direction rather than up, dunno how i screw it up lol, will update it later after further tinkering
     
  12. Solid_Metal

    Solid_Metal

    Joined:
    Mar 25, 2015
    Posts:
    45
    SO....
    while this is not the best solution...like ever lol, but for now i think this is sufficient, its not a real solution, just my hacky barbarian way of bypassing the problem lol, heres the snippet for anyone who probably face the same problem, or just curious


    Code (CSharp):
    1.  Vector3 dir = PathToFollow.path_objs[currentWaypointID].position - transform.position;
    2. Vector3 up = new Vector3(0,0,0);
    3.  
    4. if (goRight)
    5. {
    6.  up = Vector3.Cross(Vector3.forward, dir);
    7. }
    8. else
    9. {
    10. up = Vector3.Cross(Vector3.back, dir);
    11. }
    12.  
    13. Quaternion newRotation = Quaternion.LookRotation(dir, up);
    14. newRotation.x = 0;
    15. newRotation.y = 0;
    16. transform.rotation = Quaternion.Slerp(transform.rotation, newRotation, Time.deltaTime * rotationSpeed);
    thank you again guys for the help and inspiration, you guys are awesome

    but if you reading this and can give me better more effective and generally better solution, please let me know on the comment, i love to learn more :)
     
  13. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Should the up-vector of your camera actually align with the up-axis of the world? That's what the second parameter tells it to do - "the top of the camera should point approximately this way". I suspect that the only thing stopping that from working was using the wrong direction.

    Note that it's "approximately" because unless the target point, camera position and desired up direction form a 90 degree angle it can't point directly along up. (Why? Look at the ground in front of you and think about where the top of your head is pointed.)

    I'm also wary of that Slerp line. I know that it's being used exactly per the example, but in my opinion it's a terrible example that misrepresents what interpolation functions do.
     
    Last edited: Nov 16, 2017
    Solid_Metal likes this.
  14. Solid_Metal

    Solid_Metal

    Joined:
    Mar 25, 2015
    Posts:
    45
    you are correct, thats why i said on the previous post that i don't know how i screw it up lol, i supposedly use vector3.forward
    but again while your script is work correctly as i use it correctly, i still need to freeze some of the rotation which unfortunately i directly manipulate quaternion value, because of regarding of vector3 direction, it still rotate to all axis by default
    and also i still need the slerp (i assume) to interpolate the rotation smoothly between each waypoint
     
  15. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    916
    I've combed this thread and I can't see any of the important information needed to give a completely accurate answer here. is the green square's "blue arrow" its up vector and "red arrow" its down vector? you always want the object to point north (which makes me believe that this is for a 2D game).


    transform.LookAt instantly points your transform that the target. Its not meant for animating rotations toward a target over several frames.

    The 2nd parameter fed into Lookat (and LookRotation for that matter) is the direction the object should consider and orient itself to as up. its treated as a hint. these functions will always force itself to face the direction you passed as the first parameter... but objects can roll in space yet still face their target, so the 2nd parameter helps hint the object which direction it wants to roll to while still facing your target.

    Thus the vector3.up you are passing is just a hint, its secondary the the primary parameter. the object must look at the primary parameter and if that happens to be up then the object will look up. instead lock the primary parameter to Vector3.forward which will force the object to always look down the blue axis (i.e. it can only rotate on the blue axis).

    Exactly as he said you shouldn't be touching these numbers unless you understand the math behind quaternions. Remove those lines where you set x and y to 0, those do not do what you think they do. x,y,z, and w are not angles, but represent the normalized values of the complex numbers i,j,k and the real number a. changing one value actually affects the other three values. So by setting x and y to 0 you're actually forcing unity to recalculate the quaternion which may also be causing the snapping effect you are having.

    To prevent the snapping use a minimum distance threshold, where it only turns towards the current waypoint if its far enough away. Likewise the snapping is also because of Quaternion.Slerp unintentional use. with your usage the rotation is elastic, rotating faster the farther away its looking from the waypoint while slowing its rotation the closer it comes to face it. because of these two things if the object overshoots its waypoint, its rotation will "freak out".

    now unless you want that rotation to have dampening you can use RotateTowards to make the rotation more consistent.

    Code (CSharp):
    1.  
    2. float distanceThreshold = 0.2f;
    3. vector3 upDirection = PathToFollow.path_objs[currentWaypointID].position - transform.position;
    4.  
    5. //only turn towards the current waypoint when far enough away (prevents the snapping)
    6. if(lookDirection.sqrMagnitude >= distThreshold*disThreshold)
    7. {
    8.     // this will force the object to always look forward, but orient itself so its pivoting to the current waypoint (i.e. locking to the z axis)
    9.     rotation = Quaternion.LookRotation(Vector3.forward, upDirection);
    10.  
    11.     //use rotateTowards for a smooth rotation
    12.     transform.rotation = Quaternion.RotateTowards(transform.rotation, rotation, Time.deltaTime * maxDegressPerSecond);
    13.  
    14.     // or continue to use Slerp for that elastic rotation
    15.     //transform.rotation = Quaternion.Slerp(transform.rotation, rotation, Time.deltaTime * rotationSpeed);
    16. }
    17.  
    if you wanted the rotation to feel like it has some weight then you could feed the upDirection into Vector3.Smoothdamp so that the turning looks like its carrying inertia.
     
    Last edited: Nov 17, 2017
    Solid_Metal and angrypenguin like this.
  16. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,620
    Ahh nice, I didn't realise/remember that Quaternion.LookRotation(...) also allowed an up vector.
     
  17. Solid_Metal

    Solid_Metal

    Joined:
    Mar 25, 2015
    Posts:
    45
    sorry for late reply, just wanna say THANK YOU !, for the clear explanation of everything and great example code

    i just got the time to tinker with base code that you gave, theres couple line that i need to change and add new stuff, but it does works great !, and much cleaner from previous which no need for me to directly change the quaternion value