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

Confused about rotations

Discussion in 'Scripting' started by Innovine, Apr 26, 2018.

  1. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Hey guys,
    I have been struggling for a while now with rotating a Navball for my spaceship game. It looks a bit like this:


    I have tried everything I can think of to get this to show the ships current rotation, but I have to admit defeat. The best I've gotten so far is that it works correctly around the Y axis, but the X and Z were in the wrong direction. Note that the ball is not stationary in world space, it rotates in the same direction to the one the ship is rotating in.

    In the screenshot, the ship has just rotated from (0,0,0) 30 degrees around the Y axis (yaw to the right). The ball has not only rotated with the ship, but has rotated a further 30 to show the value of 30 on its display. This is the only bit that works correctly :( It all turns to poo when I start rotating on the other axes.

    For example, if I now pull my joystick back, to pitch up (rotate around the ships local X), I can't get the navball to do the same, since it's X is no longer parallel with the ships X.

    Can some kind hearted soul please explain to me how I get this to work correctly?
     
  2. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    What's the code you got so far?

    Is this thing a child of the spaceship? Have you considered just setting its global rotation to Quaternion.identity, so this way it's local rotation relative to the ship would be oriented to the global norm. So essentially relatively speaking, you'd see the inverse of the ships current rotation.
     
    Kiwasi and Pavlon like this.
  3. Pavlon

    Pavlon

    Joined:
    Apr 15, 2015
    Posts:
    191
    I think you want the ball to not rotate.
    For the ball if its the child of the ship...
    Code (CSharp):
    1. void Update ()
    2.     {
    3.         this.transform.rotation = Quaternion.identity;
    4.     }
    with the right texture on it it should show the right direction.

    Sry @ lord did only read the first line you already told it ...
     
    Last edited: Apr 26, 2018
    MoonJellyGames and lordofduct like this.
  4. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Yes the ball is a child of the ship. And nope, the ball cannot remain stationary in world space, it must rotate. If it is stationary in world space some of the rotations shown on it move in the wrong direction. And the direction it rotates in must be correct, i cant just flip the texture. When the ship rotates around its local Y to the right, the ball must rotate around its local Y to the right too, because that's how these displays work.. If the ball is stationary in world space (which I've managed) it will be displaying the inverse of the ships Y rotation, not the ships Y rotation. And I can get this working on one axis but when I start to do more complex than just rotating on Y it is very obviously wrong. The pitch and yaw seem to exchange based on how far i rotate around Y.

    I don't have any significant code, I've just been doing things like applying the ships euler angles to the balls local euler angles, applying the inverse of the ships euler angles, and various combinations.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,797
    Technically this is precisely how a gyroscopic attitude indicator works. The gyros hold it fixed while the airplane rotates around it. Obviously the ball moves POSITION with the airplane, but an ideal AI would never change its orientation.

    Assuming your ship is changing its orientation you actually DO want the AI ball to stay completely fixed in orientation in space, especially since you have compass (longitudinal) markers on it too. North will always be north, up will always be up on the ball.
     
    Kiwasi and lordofduct like this.
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    ^ What Kurt said.

    If that's NOT what you want.

    Then you need to define for us the behaviour you expect.
     
  7. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Technically, the gyroscopes are not located inside the ball. They are separate equipment, and the ball is only used to indicate attitude. It indicates pitch and yaw by rotating so that the central point appears to track along the balls surface in the same direction as it does in space. If I pitch the nose up, I expect the numbers on the ball to move from top to bottom, showing more of the light side of the ball. By the time the ship is rotated to point straight up, the ball should be showing all the light colored side.

    If the ball is made to be stationary in world space, the opposite happens. As you pitch up, you would see more of the ball from underneath, not above.
    The ball should be interpreted as if the balls normals are all flipped, and you view the inside surface of the sphere as the fixed stationary skybox, but of course that isn't possible in reality.

    The ball appears stationary in world space on the roll axis only, since the equator of the ball works as an artificial horizon.

    It may be hard to see in the image, but if the north (zero yaw) line is in the center of the display, 90 degrees (east) is to the _right_. If the ball is stationary and the ship rotates from north to east, using the current, correct texturing) the West 270 line would become visible, moving in from left to right.. What really should happen is that the ship rotates around to 90, and the ball follows along since it's a child of the ship, also rotating to world space 90. To display the angle, the ball is required to rotate a further 90, bringing the East line in from right to left (as everything outside is apparently moving right-to-left, too.

    Hope that explains the problem better. I am 100% certain that the direction of rotation for pitch and yaw are backwards if the ball is stationary, but I'm running into problem determining pitch from yaw once i have rotation on more than one axis.


    Put another way, if the ship pitches up, the ball should apparently rotate its texturing from top to bottom, showing more of the light side. If the ship yaws right, the texture should rotate right to left, showing 3, then 6, then 9, then 12... If the ship rolls clockwise, the texture rotates anti-clockwise. All these ship rotations are relative to its current attitude.. i think some of my problem is that the axes the ball rotates around are actually rotating. Pulling the joystick back, ie pitching the ships nose up, is always indicated by a rotation so the texture moves from the top of the control panel to the bottom, which is a rotation around an axis parallel with the face of the control panel, but as soon as i yaw the ball, that axis is moving. I am unable to rotate the ball as if it is driven by little wheels in the top and side of the control panel, ie, the axis to rotate around is always fixed in the ships coordinate space, and if I could do that, how would the ships world orientation correspond to the required ball input angles?
     
    Last edited: Apr 27, 2018
  8. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    Attitude indicators don't directly indicate what the internal gyro is rotating to. indeed a gyro would try to keep the same rotation (along its spinning axis) but the display would show the inverse of that. depending on the indicator (ones which hold the gyro in a ladle), some completely wonk out if you try to do a loop causing them to flail wildly (as you broke the gryo's precession).

    @Innovineit, it appears your indicator is a simple ball attitude. You can likely get away with just grabbing the plane's world rotation setting it as the indicator's local rotation on update. the result should appear as you desired.
     
  9. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Dude, I appreciate the attempt to help, but i just wrote a huge wall of text explaining why that is not the solution...
     
  10. angrypenguin

    angrypenguin

    Joined:
    Dec 29, 2011
    Posts:
    15,516
    I don't think they said what you think they said.

    He's talking about getting the world rotation of the ship and setting it as the local rotation of the indicator. So if the ship rotates to, say, -90 on Y the indicator will now be -90 on Y in local space and -180 on Y in world space.

    This solution assumes that the ship model is aligned with its own local space (ie: if you just drop the model in Unity, do the "forward", "up" and "right" directions of the ship line up with the Z+, Y+ and X+ axes of the gizmo when it's selected?) and that there are no other rotated transforms in the hierarchy between them. Though even in those cases you can still calculate the rotation you need (see the Transform class).
     
  11. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    That could easily be the case, this stuff is really difficult to put into words!!

    I have tried this:
    Code (CSharp):
    1. navball.transform.localEulerAngles = ship.transform.eulerAngles;
    This definitely does not work as desired. It initally works ok when just rotating around Y, but as soon as any other rotation comes in it goes wrong. I am not at my Unity project to test it again but it was one of the things I tried. I think it also gets really out of whack if rotating on multiple axes. I also tried this with quaternions instead of euler angles, but I am unsure if i did it right. I think maybe it was working but didn't work past 180 degrees? I'll try again after work and report the exact code and result.

    I believe this is the case, but I'm also exporting from blender to unity and sometimes the fbx's are a bit confused. From what I remember, the scales in every step of the hierarchy were 1, and the rotations were all 0's. I do have a vague memory of the Z pointing the wrong way for one of the objects, another thing i will definitely have to check out...
     
  12. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    So I don't know how navballs work. Not a pilot, nor do I play flight sims. I just assumed it was a gyroscope.

    Which is why I asked for a definition.

    What I do know though, is rotations.

    ...

    And thanks for the definition.

    ...

    So for starters I avoid euler angles like the plague because they just don't work well with the maths. Especially when trying to unravel information like this. You need to concern yourself with the order unity puts the euler angles together (yaw->pitch->roll? Is it pitch->yaw->roll? What is it? I don't know... I don't care, I don't want to care. I just avoid them because they're the devil regardless).

    So...

    Based on your definition, and some videos I looked at, I take you to mean:

    yaw is the inverse measure of your forward off of global forward (north in real world terms), as projected onto the ground (redact y information).

    pitch is the inverse measure of your ships forward against the ground, which can be defined as a vector in the direction of that flattened forward we used for yaw

    Which means we'd get:
    Code (csharp):
    1.  
    2.     [SerializeField]
    3.     private Transform ship;
    4.  
    5.     [SerializeField]
    6.     private Transform navball;
    7.  
    8.     private void Update()
    9.     {
    10.         var forw = this.ship.forward;
    11.         forw.y = 0f;
    12.         if (forw.sqrMagnitude > 0.0001f) forw.Normalize();
    13.  
    14.         var yaw = -Vector3.SignedAngle(forw, Vector3.forward, Vector3.up);
    15.         var pitch = Vector3.SignedAngle(forw, this.ship.forward, this.ship.right);
    16.  
    17.         var rot = Quaternion.AngleAxis(pitch, Vector3.right) * Quaternion.AngleAxis(yaw, Vector3.up);
    18.         this.navball.localRotation = Quaternion.Slerp(this.navball.localRotation, rot, 0.5f); //used a little slerp to give a smoother look
    19.     }
    20.  
    Resulting in something like this:
    Navball.gif

    Is that what you described?

    What I did was used some trig to calculate the angles around each (I used unity's built in function for it... it's actually not 100% accurate, they have optimizations done on it to speed it up which loses a tiny bit of accuracy. But it's fine, and it's better than me writing all the trig. Anyways, if you sanitize the inputs before hand the inaccuracies pretty much disappear, which I do.)

    And with those angles around their respective axes I create Quaternion's around the appropriate axes for pitch and yaw (right and up respectively), and set it to the local rotation.
     
    Last edited: Apr 27, 2018
  13. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Ooo, squeeeee! That is looking exactly as I meant..

    Does it continue to work if you roll over sideways? :) If you roll over 90 (at any yaw value) so the horizon appears vertical on the screen, the sphere should also be divided vertically (relative to the screen) down the middle, with the black on the ground side. In this position, if you pitch up and down, that division between the hemispheres stays where it is, with the ball rotating so the light hemisphere stays on one side, and the dark on the other. The vehicle will be rotating around the world vertical axis, you see the ground out one side and the sky on the other the whole time you rotate, and the navball reflects this by having the ground hemisphere on one side and the sky on the other.

    Sorry if my terminology is confusing, I am struggling very much to convey what I mean here :)

    I'm sorry but I am not sure I follow this, and am not sure if it is correct... The pitch isn't related to the ground as far as I can tell. If you are in a plane and you pull the joystick back towards yourself (pitching up), the plane rotates around the axis running through the wings. It will do this no matter which world orientation the plane is currently in (ignoring aerodynamic forces for simplicity). So for example... If you are flying parallel to the ground, right way up, and in any compass direction, when you pull the stick back to pitch up the nose goes up towards the sky and the aircraft gains altitude. More of the sky hemisphere of the navball becomes visible, exactly as you demonstrate in your video.

    But if you do this when flying parallel to the ground but wrong side up, and you again pull the stick back towards yourself, now the nose goes down towards the ground and you lose altitude. But in both cases the navball will rotate around the axis through the wings, and with more of the top portion becoming visible. If I am flying up to the sky, I'll see more of the light hemisphere coming into view. If I was inverted (rolled 180) the ground hemisphere will be on the upper part (relative to the screen) and more of the ground will come into view, from the top, relative to the screen. Is this the same as your statement? :)

    Finally, if you are flying on your side, one wing pointing directly down and one pointing directly at the sky zenith, (not a position you'll remain flying in very long..) and you pull back the stick, you will rotate through all the compass values, but the nose will remain pointed at the horizon, and again the navball rotates in the same way, with more texture appearing from the top (relative to the screen).
     
    Last edited: Apr 27, 2018
  14. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    Well for starters, it'd be trivial to give a try yourself and see if it acts as you believe.

    As for what you say though... if I interpret what you say correctly, no, it would not behave in that manner.

    So if like you're upside down, it should act the same as if you were right side up. Hrmmm.... how does... OHHHHHH. I get it, ok ok, it IS based on a gyroscope. But the gyroscope is bolted to the plane!

    DERP!

    I mean, of course it is... where the hell else would it be bolted?

    lol

    [edit]
    OK, so this actually creates a problem. This means your navball isn't actually based on orientation. This means your navball is the result of consecutive accelerative forces over time. Which explains its wobbily motion in the videos I'm watching.
     
  15. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    I'm stuck at work for the next 6 hours, and THEN!! oh believe me I can't wait to get home.

    Isn't it great when things click like that and it starts to make sense :)
    The gyroscope is feeding the navball the rotations, but the navball itself is rotating in a different way, so that it's movements match the movements of the scenery outside.

    I sure picked a hell of a starter project to learn this with.
     
  16. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    It's 5am here, and I'm a bit drunk... so yeah you might have to wait for me to wake up again to come back to this.

    But I'll be back later to see how this goes.
     
  17. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Great, get some sleep, and we'll check it out later. Your help is massively appreciated, thanks!!!
     
  18. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    In reality yes, it's using the gyroscopes to sense rotation, but since the point is to show the pilot the vessels orientation in world space, a perfect navball has an exact 1-1 mapping from the vessels world space orientation to navball position. If only I could figure out what it is..
     
  19. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    So before I go to bed I decided to come back in here and write up the foundation of what it was I was talking about.

    Note though, due to float error it'd accumulate float error over time and slowly get out of whack. But this is the basic foundation of what I presume you mean.

    As I said, this isn't based on any specific orientation (hence why all attempts up to now don't work). It's based on accumulated rotation over time.

    Now maybe there is a way to 1-1 map this to world space orientation. But that doesn't sound right to me. As you stated that the rotation is based of its own rotation around its own wings... and not relative to the ground. If it's not relative to the ground, that means it's not an actual orientation. Orientations are by definition relative to something. This is why when it flips to the side or upside down it'd act in the manner as you describe... it's motion is relative to itself.

    Which makes sense to the engineering of it. If you have a gyroscope attached to a rigid body, and you want to measure orientation. Then you aren't actually caring about gravity (which was all of our original suggestion... using gravity to determine information... orienting relative to the world). But instead you'd use the gravity to suss out the pull occurring on the body by gravity.

    ...

    Mind you I'm slammered right now.

    So maybe my brain is working backwards.

    Anyways:
    Navball.gif

    I think this is what you described. And if my assumptions about the physics of it make sense, this should be it.

    Code:
    Code (csharp):
    1.  
    2. using UnityEngine;
    3.  
    4. public class Navball : MonoBehaviour
    5. {
    6.  
    7.     #region Fields
    8.  
    9.     [SerializeField]
    10.     private Transform ship;
    11.  
    12.     [SerializeField]
    13.     private Transform navball;
    14.  
    15.     private Quaternion _last;
    16.  
    17.     #endregion
    18.  
    19.     #region CONSTRUCTOR
    20.  
    21.     private void Start()
    22.     {
    23.         this.ResetNavball();
    24.     }
    25.  
    26.     #endregion
    27.  
    28.     #region Methods
    29.  
    30.     public void ResetNavball()
    31.     {
    32. //this is naive, resetting would require some heavier calculations about where north is,
    33. //and to adjust for if the ship is currently pitched or rolled
    34. //basically this needs to be called with the ship sitting at normal orientation in its current state
    35. //I'm assuming a plane probably does this at startup as well, auto calibrating its initial orientation
    36.         navball.localRotation = Quaternion.identity;
    37.         _last = ship.rotation;
    38.     }
    39.  
    40.     private void Update()
    41.     {
    42.         var delta = Quaternion.Inverse(_last) * ship.rotation;
    43.         navball.localRotation = delta * navball.localRotation;
    44.         _last = ship.rotation;
    45.     }
    46.  
    47.     #endregion
    48.  
    49. }
    50.  
    Tomorrow when I'm more coherent I may see how this relates to orientation. But I really don't think it will. Or well... I mean we could suss it out by just storing the initial rotation at the start and measure the delta from that...

    Maybe that'd work?

    Nah, that'd make the pitch relative to the world again, not itself.
     
    Last edited: Apr 27, 2018
    JoshuaMcKenzie likes this.
  20. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Looks nice, the pitch and yaw are correct but the roll is backwards :)
     
  21. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Well, there is a single unique navball orientation for every world orientation of the vessel, which is why i think there is a conversion function there. The pitch of the vessel is measured as a rotation around the wing axis, and that axis and the zero rotation point can be expressed in world space (with zero pitch on the ball being when the nose points at the horizon). The rotation of the vessel around the wing axis gets directly used to rotate the ball around that same wing axis to show the pitch, NOT taking the world space pitch and displaying it on the ball, if that makes sense. It is the fact that the wings axis is free to move which makes the absolute world orientation uninteresting, but the orientation of that axis and the rotation around it is what is to be displayed on the ball :/

    Maybe I'm just confusing it more, now, but i think this is what you're getting at too with the code above.
     
    Last edited: Apr 27, 2018
  22. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Ok, so I have now verified that there are no weird transforms in the hierarchy, everything is zero'd and the scales are all 1.

    I've tested the code above and it works flawlessly EXCEPT that the roll is in the wrong direction, which is the same as shown in your video..
     
  23. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,797
    I apologize for oversimplifying in my first post above.

    The actual solution was more complicated than I realized, but the ball does not move in my solution.

    - attitude indicator ball fixed in space (never rotates)

    - a common camera rig with two cameras parented below it:
    ---- main camera facing +Z the way you would expect it
    ---- AI camera facing -Z basically 180 degrees different

    Use layers to make the AI camera only see the AI ball, and the main camera see everything else.

    WARNING: You must make a layer called AIBALL at UserLayer10 to have this work!

    The above gets you Yaw and Pitch correct but Roll is the wrong way.

    Therefore I wrote a script to drive the AI camera anti-Z (TrackAntiZ.cs).

    When you turn the direction of the camera rig, the AI camera will show the "correct" side of the AI Ball.

    See onscreen instructions for the simple controller. It is still subject to gimbal lock because I am tracking Yaw/Pitch/Roll interdependent of the resultant rotation.

    See attached unitypackage. That attitude texture is the first one off Google Images, with E/W reversed.
     

    Attached Files:

  24. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    that's a really nice way of going about it.

    I added one extra line to lordofduct's code so that it can roll properly.

    Code (csharp):
    1.  
    2.     private void Update()
    3.     {
    4.         var delta = Quaternion.Inverse(_last) * ship.rotation;
    5.  
    6.         delta.z = -delta.z;
    7.  
    8.         navball.localRotation = delta * navball.localRotation;
    9.         _last = ship.rotation;
    10.     }
    11.  
    Essentially what this does is after finding the delta rotation I simply invert the z component for that quaternion which will cause it to roll in the opposite direction
     
  25. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Nice, didn't know you could mess with the z of the quaternion like that. Looks great now guys, thanks a lot!!! Your help has been absolutely wonderful!

    I need a bit of time to chew through what's happening here before I fully understand all the details, but its working great and I'm supper happy, thanks :)
     
  26. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    its encouraged not to mess with the components of a quaternion unless you really know what you are doing, instead relying on all the helper functions in the Quaternion library to handle rotations.

    that said, they are still accessible incase one wanted to modify them directly and this was such a special case where modifying the component was actually the simplest solution.
     
  27. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    I am obviously not understanding this :(
    I am now trying to figure out the rate of rotation around the ships axes... but this isnt delta.x, delta.y and delta.z ?
     
  28. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Ok so I sort of have this working at last :)
    I am using delta.x * rateScale / Time.deltaTime as the rate at which I am pitching around, and then I convert this into an angle on my pitch rate meter.. This is working reasonably well, but on some frames the delta value is 0 or something quite low, which is causing my pitch rate meter indicator to jerk to 0 for a single frame every now and then.

    I can't quite figure out why this is.. any idea?
     
  29. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    The xyzw components are not angles. The components are complex numbers (think imaginary: ie the square root of -1). each component should only range from -1 to +1 which it oscillates (think of a sin wave on a graph). Plus the more that one component has, the less the other two should have relative to w, ALL the components are interconnected. And the total magnitude of the quaternion's components should be fixed.

    When I inverted delta.z it worked because the overall magnitude remained the same. I didn't add, remove, or have to do any other complicated math cause my change kept its magnitude stable. By adding/removing on delta.x, the Quaternion likely detects that its magnitude is wrong and thus recalculates to re-balance it, resulting your changes being re-distributed across all other components. because of this you're actually accumulating error and causing an artificial gimbal lock (which is the jerk you're seeing).


    This is why I said its better to use the Quaternion helper functions. I should have clarified that you shouldn't mess with the components unless you really understand the actual math of quaternions. theres alot more going on with them.
    you should already have that. it'd be the same euler angles you'd put into the ships transform.rotate.
     
    lordofduct likes this.
  30. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    So I came back in here.

    And yeah, inverting the z would work.

    I also cleaned this up to be calculated based off current orientation. Before when I was talking about the relative nature and what not... I was confused by you saying that rotation was relative to itself, but insisting it should be able to be calculated based on the ship orientation which is world relative.

    Thing is, googling around for what this thing is, I kept looking at information about an 'Attitude Meter'. When really this is an "AHRS" (Attitude & Heading Reference System). It's not a attitude meter, it's a combined compass AND attitude. And the attitude is relative to the self, but the heading is relative to the world.

    And of course, once sober, looking at the code... it can just be simplified down.

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class Navball : MonoBehaviour
    7. {
    8.  
    9.     #region Fields
    10.  
    11.     [SerializeField]
    12.     private Transform ship;
    13.  
    14.     [SerializeField]
    15.     private Transform navball;
    16.  
    17.     [SerializeField]
    18.     [Range(0.01f, 1f)]
    19.     private float _slerpSpeed = 0.15f;
    20.    
    21.     #endregion
    22.    
    23.     #region Methods
    24.    
    25.     private void Update()
    26.     {
    27.         var rot = ship.rotation;
    28.         rot.z = -rot.z;
    29.         navball.localRotation = Quaternion.Slerp(navball.localRotation, rot, _slerpSpeed);
    30.     }
    31.    
    32.     #endregion
    33.  
    34. }
    35.  
    I even added in a little slerp to give it that bouncy effect you seen in real life.
     
    neohelios likes this.
  31. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    So...

    A Quaternion is to rotation in a similar manner to how a vector is to position.

    If in math class you were taught about a vector, the mantra that is often belted into your brain is that:
    "A vector has magnitude and direction!"

    A vector is not a point/position, it is a vector. Meaning that it represents the change in position. When we say that a GameObject has a position in the form of a vector, what we're really saying is that the GameObject is at a position that direction and magnitude from the origin.

    This is the same for Quaternion's. A Quaternion represents the change in rotation from 'identity'. So therefore it really doesn't show orientation, it shows how much one had to rotate from identity to get to this orientation.

    This is why you can append Quaternion's with the * operator. You can say:

    Code (csharp):
    1.  
    2. var a = Quaternion.Euler(0f, 90f, 0f);
    3. var b = Quaternion.Euler(30f, 0f, 0f);
    4. var c = a * b; //you will get (30f, 90f, 0)
    5.  
    What this is actually saying is "Yaw 90, then pitch 30" in that specific order.

    Note though, this means order of operations means something:
    Code (csharp):
    1.  
    2. var d = b * a;//you will get (0f, 90f, 30f)
    3.  
    So wait... why does this give me 30 around z instead of around x? 'b' says to rotate around the x axis.

    Not really, what you said was "Pitch 30 degrees, then yaw 90 degrees."

    Well think about that. If you pitched 30, you're now pointing downish. If you then yaw'd 90 around your up (not the world up) you're now in an orientation where you look rolled by 30.

    This means that if you append a value, you rotate locally. If you prepend a value, you rotate globally.

    In this spoiler I'm going to go into how unity translates between quaternion notation and euler notation... and why I avoid it like the plague:

    Note that if you convert a quaterion to euler. Each component is unwrapped in a specific order as 3 axes rotations in a specific order:
    https://docs.unity3d.com/ScriptReference/Quaternion-eulerAngles.html

    So it unwraps roll, pitch, yaw.

    This means the orientation is actually calculated in the order yaw, pitch, roll. It's essentially saying that these 3 lines are equal:
    Code (csharp):
    1. Quaternion.Euler(10f, 20f, 30f)
    2. Quaternion.Euler(0f, 20f, 0f) * Quaternion.Euler(10f, 0f, 0f) * Quaternion.Euler(0f, 0f, 30f)
    3. Quaternion.AngleAxis(20f, Vector3.up) * Quaternion.AngleAxis(10f, Vector3.right) * Quaternion.AngleAxis(0f, 0f, 30f)
    So if the euler says:
    (0f, 90f, 30f)

    It's actually saying "yaw 90f then roll 30f".

    This is why if you had done this instead in my previous example:
    Code (csharp):
    1.  
    2. var a = Quaternion.Euler(0f, 0f, 30f);
    3. var b = Quaternion.Euler(0f, 90f, 0f);
    4. var c = a * b; //you will get (-30, 90f, 0f)
    5.  
    You get a pitch of -30f if you flip the operations.

    You said "roll 30f, then yaw 90f", but when unraveled we need to ensure that the resulting euler resolves in the order yaw, pitch, roll. Which means we unravel roll, pitch, yaw.

    But our build order was wrong. We told our rotation to roll before yawing. But unity calculates eulers yaw before roll. So when we unravel, it tries tries to get the roll... and well, its current orientation actually had its roll undone by that yaw, turning it into a pitch. So no roll is unraveled.

    What is my point?

    This means that any time you try to measure an objects rotation around a specific axis. You have to consider what that really means. An euler.z value of X doesn't mean X degrees around z. It means X degrees around the local z after y, and x have been applied! It's not your roll, it's your roll relative to pitch and yaw. It's not your pitch, it's your pitch relative to yaw. The only one guaranteed to be independent is yaw. Which makes sense since most games are built in a manner that +y is up. And you want to be able to do things like turn, so you can just add degrees to the y and you don't get strange behaviour.

    rot.y += X

    Means rotate X degrees around <0,1,0>.

    rot.z += X

    Means rotate X around transform.right, and transform.right is going to be whatever x and y dictate it to be.

    So, with all that you can see what I think euler angles are garbo.

    It isn't that euler's increase the odds of gimbal lock (though yes, that's annoying... and is what many people quote as the reason to use quaternions). They're annoying to me because I have to constantly be thinking about "What does it really mean to rotate around x or z at this given moment?"

    Where as in quaternion, if I want you to rotate around the global x I can say:
    Code (csharp):
    1. transform.rotation = Quaternion.AngleAxis(30f, Vector3.right) * transform.rotation
    And if I want to say rotate around the local x I can say:
    Code (csharp):
    1. transform.rotation *= Quaternion.AngleAxis(30f, Vector3.right);
    Furthermore I can rotate around arbitrary axes... both global and local.

    So, with my rant about euler angles over.

    From that rant we learned that euler's come with this issue of needing to constantly consider the order in which unity converts euler to quaternion. And that being yaw, pitch, roll. Meaning that yaw is the only truly independent variable, and the others always being contextual.

    Where as quats allow me to control if I want to rotate around a given axis, any axis (even arbitrary), independent of the current orientation. Not just yaw.

    So wait... how then do we measure the rate of change around a specific axis from this quaternion?

    Well we need to define some things:

    rate of rotation - this implies that we want to know the delta over some time, so the difference between frames
    around the ship's axes - well, which ones?

    So, lets say we want to measure how much we pitched around ourselves (relative to our right, not the world right).

    Well for starters we need to get our local right:
    Code (csharp):
    1. Vector3 localRight = transform.rotation * Vector3.right
    All we've done is rotate global right by as much as we are rotated to get our local right.

    Note, there is a short cut for this:
    Code (csharp):
    1. Vector3 localRight = transform.right;
    Where transform.right is really just a shortcut (the decompiled Transform code for right):
    Code (csharp):
    1.  
    2.     /// <summary>
    3.     ///   <para>The red axis of the transform in world space.</para>
    4.     /// </summary>
    5.     public Vector3 right
    6.     {
    7.       get
    8.       {
    9.         return this.rotation * Vector3.right;
    10.       }
    11.       set
    12.       {
    13.         this.rotation = Quaternion.FromToRotation(Vector3.right, value);
    14.       }
    15.     }
    16.  
    And well, if we allow this 'right' axis to represent a normal of a plane. We've just defined a 2d surface we can do simple 2d geometry on. Now all we need is some vectors in that 2d space that we can test the rotation of over time.

    And well... we have 2 vectors on that plane defined for us.

    transform.up and transform.right!

    So if we take those vectors and perform some linear algebra on them converting them to the 2d representations we can then measure the atan of the change in y over x to get the amount of change.

    OK... lets pull out our linear algebra text book!

    ...

    OR

    Lets just use a nice little helper function unity gave us (which was only recently at that... which is why my sp framework also has a version of this):
    https://docs.unity3d.com/ScriptReference/Vector3.SignedAngle.html
    Code (csharp):
    1. public static float SignedAngle(Vector3 from, Vector3 to, Vector3 axis);
    So it takes a from, a to, and an axis.

    That axis is our right, the from is the previous vector, and the to is our next.

    So:
    Code (csharp):
    1. var a = Vector3.SignedAngle(_lastRotation * Vector3.up, transform.up, transform.right);
    This is the amount of change in degrees we went from last rotation to current rotation around the current right. The sign of which will be the direction.

    If all we did was pitch in that time, we could do this:
    Code (csharp):
    1. var rot = _lastRotation * Quaternion.AngleAxis(a, Vector3.right);
    And rot should equal the current rotation (with maybe some float error).


    ...

    ...

    In the end, just keep in mind that though in 2d angles are super helpful. They're beneficial though only because there is a clearly defined global orientation around which that angle is being rotated.

    All orientations (euler angles being a measure of orientation, rather than magnitude and direction of rotation)... are relative to something. And in 3d there's no true clearly defined global to which anything can be easily defined that makes sense. So really you're always working with data dependent on other data.

    So when you want to measure some amount of rotation. Well first you need to consider what is that measure oriented off of? Since the quat is only the magnitude and direction, we need to know the 'from what' you intend to be thinking of that quat as. From global identity? From last orientation? From some arbitrary orientation (like if you defined north to be <0.7071,0.7071,0> like some mad man)?

    If you don't define the 'from what'. You're going to be lost. It's sort of the definition of lost, lost... lacking an orient to base your location off of.

    This is why in my previous posts we kept coming back to the question of what things are oriented against. And how no clear definition of that orient makes for difficult work. I basically had to shoot in the dark, half cocked, and trip over what it is you were intending.
     
    Last edited: Apr 29, 2018
  32. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Thanks @lordofduct, that was really, really helpful, and is giving me plenty to chew on.

    I notice one thing now, the above solution is very jittery just around 0 (not when at zero). Is that due to SignedAngle having trouble when the difference in the rotations is really small, or do i have a bug somewhere?
     
  33. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Heheh, oh noes, this doesn't work either :)

    Code (CSharp):
    1. var a = Vector3.SignedAngle(_lastRotation * Vector3.up, transform.up, transform.right);
    This is working when I start off at 0,0,0. But if I roll, it will show in 'a' here too.

    Here is my code, which is on the Pitch rate indicator, and it's supposed to show a positive pitch rate, or a negative pitch rate, and not do anything for rolls and yaws:
    Code (CSharp):
    1.             angle = Vector3.SignedAngle (_lastShipRotation * Vector3.up, ship.transform.up, ship.transform.right);
    2.             angle = -angle * rateScale;
    3.             transform.localRotation = Quaternion.Slerp (transform.localRotation, Quaternion.Euler(0, angle+180, 0), _slerpSpeed);
    The slerp is helping to smooth out those jitters when its close to zero but it's not perfect.
    According to what I get from your text above, this is showing me the angular difference around the ships x axis, between the ships up, and the ships previous up. But if the ship is rolling around z, isn't ship.transform.up different every frame? So if only movement going on is rolling in the ships z, the above code will effectively count that as the pitch too?
    It is not counting movement on yaw, which kinda makes sense.

    My brain is totally burned out now :(
     
  34. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    I don't have a lot of time to dedicate to this anymore.

    I'll take a guess at what's causing the problem, but consider this the end of me getting involved in this thread.

    Code (csharp):
    1.  
    2. angle = Vector3.SignedAngle (_lastShipRotation * Vector3.up, ship.transform.up, ship.transform.right);
    3. angle = -angle * rateScale;
    4. transform.localRotation = Quaternion.Slerp (transform.localRotation, Quaternion.Euler(0, angle+180, 0), _slerpSpeed);
    5.  
    I'm assuming this is a dial that measures the current rotational velocity as some scale. And that scale probably scales up (rateScale > 1f) so that the dial is more pronounced.

    This means any float error, and any trig error will be more pronounced as well.

    Here's the thing... this is unity's version of SignedAngle:
    Code (csharp):
    1.  
    2.     /// <summary>
    3.     ///   <para>Returns the signed angle in degrees between from and to.</para>
    4.     /// </summary>
    5.     /// <param name="from">The vector from which the angular difference is measured.</param>
    6.     /// <param name="to">The vector to which the angular difference is measured.</param>
    7.     /// <param name="axis">A vector around which the other vectors are rotated.</param>
    8.     public static float SignedAngle(Vector3 from, Vector3 to, Vector3 axis)
    9.     {
    10.       Vector3 normalized1 = from.normalized;
    11.       Vector3 normalized2 = to.normalized;
    12.       return Mathf.Acos(Mathf.Clamp(Vector3.Dot(normalized1, normalized2), -1f, 1f)) * 57.29578f * Mathf.Sign(Vector3.Dot(axis, Vector3.Cross(normalized1, normalized2)));
    13.     }
    14.  
    It measures the angle between 'from' and 'to', and it multiplies that by the direction around axis.

    This means their algorithm assumes that axis is orthogonal (perpendicular in 3-space) to both 'from' and 'to'.

    But if you're both pitching and rolling at the same time... it isn't orthogonal to both, only to the current up.

    The amount of error is small enough that it shouldn't be a huge issue... I actually mentioned this in a way earlier post about it:
    This is why I have my own version in my sp framework... well that and I wrote mine before unity included theirs.

    Here's mine:
    Code (csharp):
    1.  
    2.         /// <summary>
    3.         /// Find some projected angle measure off some forward around some axis.
    4.         /// </summary>
    5.         /// <param name="v"></param>
    6.         /// <param name="forward"></param>
    7.         /// <param name="axis"></param>
    8.         /// <returns>Angle in degrees</returns>
    9.         public static float AngleOffAroundAxis(Vector3 v, Vector3 forward, Vector3 axis, bool clockwise = false)
    10.         {
    11.             Vector3 right;
    12.             if(clockwise)
    13.             {
    14.                 right = Vector3.Cross(forward, axis);
    15.                 forward = Vector3.Cross(axis, right);
    16.             }
    17.             else
    18.             {
    19.                 right = Vector3.Cross(axis, forward);
    20.                 forward = Vector3.Cross(right, axis);
    21.             }
    22.             return Mathf.Atan2(Vector3.Dot(v, right), Vector3.Dot(v, forward)) * MathUtil.RAD_TO_DEG;
    23.         }
    24.  
    https://github.com/lordofduct/space...epuppyUnityFramework/Utils/VectorUtil.cs#L289

    You may notice they're very very different.

    Well I actually get the right and forward orthogonal to axis. And I flatten out v onto this so that I'm measuring ONLY that.

    Unity probably doesn't do this to save on overhead... my version is a lot more costly, while attempting to remove margin of error. Or rather I'm getting the actual angle around 'axis' and 'only axis', while ignoring all other rotation. Where as unity is giving you the angle between, and the direction around some arbitrary axis. Which is why mine is called 'AngleOffAroundAxis', and there's is 'SignedAngle'. Technically speaking... theirs isn't trying to be what mine is, and its name reflects that.

    I really only showed 'SignedAngle' because it was 1) directly available in your code base without having to reference some 3rd party code, and 2) its accuracy is usually close enough, but since you scaled it, you're getting a lot of jitter.

    ...

    Try using that instead of SignedAngle, it might be more accurate.
     
  35. Innovine

    Innovine

    Joined:
    Aug 6, 2017
    Posts:
    522
    Naa, the scaling is a linear scaling like 1degree per second, or 5 degree per second, changing the sensitivity of my rate display. The jitter appeared when the value was close to, but not equal to 0, that is, when the difference was really small. It worked fine if the difference was like a half degree of rotation per frame and faster, but really jittery under that. I didn't manage to figure out what caused it.
    I ended up rewriting the needle code to:
    Code (CSharp):
    1.  
    2.         Quaternion delta = Quaternion.Inverse (_lastShipRotation) * ship.transform.rotation;
    3.  
    4.         float deltaX = delta.eulerAngles.x;
    5.         if (deltaX > 180) {
    6.             deltaX = deltaX - 360;
    7.         }
    8.         deltaX *= -rateScale;
    9.         deltaX = Mathf.Clamp (deltaX, -clampRange, clampRange);
    10.         transform.localRotation = Quaternion.Slerp (transform.localRotation, Quaternion.Euler(0, deltaX + 180, 0), _slerpSpeed);
    11.         _lastShipRotation = ship.transform.rotation;
    12.  
    This is now, finally, working smoothly and nicely, and I am super happy! Could not have done it without your help, so thank you very much! You not only helped out in solving my problem but also provided loads of very useful advice to help me understand. Much appreciated!
     
    Last edited: Apr 29, 2018