Search Unity

Is Vector3.SignedAngle() working as intended?

Discussion in 'Scripting' started by cricketspike, Jun 13, 2019.

  1. cricketspike

    cricketspike

    Joined:
    Apr 5, 2017
    Posts:
    9
    Code (CSharp):
    1.         Vector3.SignedAngle(new Vector3(1,0,0), new Vector3(1,1,0), Vector3.up);
    This returns 45 for me, shouldn't it return 0 as these line up around the up axis?
     
    juan-jo likes this.
  2. IcePhoenix_0101

    IcePhoenix_0101

    Joined:
    Sep 9, 2017
    Posts:
    32
    I can understand your trouble. But Vector3.SigendAngle() is working as intended.

    The axis parameter in this function is only used to calculate the sign, not the angle. The angle calculation is done the same as for Vector3.Angle, so it doesn't matter if the vectors line up on the axis.

    For the given vectors this function will never return 0. It will return 45 for up, down, left, right & forward axis. And -45 for back axis.
     
    Ermiq and juan-jo like this.
  3. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    Here is the source for SignedAngle:
    https://github.com/Unity-Technologi...ob/master/Runtime/Export/Math/Vector3.cs#L309

    Code (csharp):
    1.  
    2.         // The smaller of the two possible angles between the two vectors is returned, therefore the result will never be greater than 180 degrees or smaller than -180 degrees.
    3.         // If you imagine the from and to vectors as lines on a piece of paper, both originating from the same point, then the /axis/ vector would point up out of the paper.
    4.         // The measured angle between the two vectors would be positive in a clockwise direction and negative in an anti-clockwise direction.
    5.         public static float SignedAngle(Vector3 from, Vector3 to, Vector3 axis)
    6.         {
    7.             float unsignedAngle = Angle(from, to);
    8.  
    9.             float cross_x = from.y * to.z - from.z * to.y;
    10.             float cross_y = from.z * to.x - from.x * to.z;
    11.             float cross_z = from.x * to.y - from.y * to.x;
    12.             float sign = Mathf.Sign(axis.x * cross_x + axis.y * cross_y + axis.z * cross_z);
    13.             return unsignedAngle * sign;
    14.         }
    15.  
    As you can see all it does is get the angle, and then determines the sign by dotting the axis against the cross product of from and to.

    It always returns the shortest angle between from and to, sign is just guessed (I say guessed because it does not test the orthogonality of the axis, nor respect any projections done with it).

    If you want an angle between to axex, as if they were projected onto a planar surface defined with a normal equal to 'axis'. What you'll need to do is project those vectors onto said planar surface and measure that angle.

    Here I do that:
    https://github.com/lordofduct/space...epuppyUnityFramework/Utils/VectorUtil.cs#L297

    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.  
    'forward' would be your 'from', and 'v' your 'to'.

    What I'm doing is getting a 'right' vector on the plane defined by axis, and then projecting forward onto that same plane. Now forward is technically the 'forward' is our x-axis of our 2d plane, and 'right' is our y-axis. If we dot our vector onto these we get their magnitudes in those 2 respective directions and they can be used as the y and x values of Atan2.

    Note, the method assumes all vectors are normalized... I should probably put that in the documentation.
     
  4. StuartHarrison

    StuartHarrison

    Joined:
    Oct 7, 2019
    Posts:
    1
    If we ever meet in person, allow me to buy you a beer or three. Many thanks .. :cool:
     
  5. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    same basic premise as lordofduct's code (edit: actually it's one Cross cheaper than his variant)
    Code (csharp):
    1. // dir1 dir2 = need to be unit vectors
    2. // assumed to start at the exact same point; their direction matters
    3. // the result is in radians
    4. static public float CalcSignedCentralAngle(Vector3 dir1, Vector3 dir2, Vector3 normal)
    5.   => Mathf.Atan2(Vector3.Dot(Vector3.Cross(dir1, dir2), normal), Vector3.Dot(dir1, dir2));
    we'll probably never meet, but I could use a beer or two, sadly didn't make it in time.
     
  6. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,532
    I don't drink beer. I drink vodka.

    And the reason I have another cross is to ensure forward and right are on the same plane defined by axis, otherwise your measure could be inaccurate relative to that plane/axis.

    Your approach would require dir1/dir2 to be orthogonal to axis and normalized (dir1 dot dir2 if not normalized is a problem) to get similar results to my algorithm. Which the user may not know, or the values will represent a different thing, which the user may not know.

    If you take OPs inputs and start changing the axis around to non-orthogonal values. You'll get different results between both of ours. Such as if you put (sqrt(2), 0, sqrt(2)) mine outputs 54.7 degrees, yours 35.26 degrees. It's because we're representing 2 different things. This isn't to say yours is wrong... it's to say they're different behaviours.

    Mine specifically was designed to work on a arbitrary curved 3d surface where we know the up (the ground normal), and we know some general forward (the player's look direction) and we know some general direction to some target (a mob in the distance) and we want to know the angle relative to the ground to turn towards that enemy (for example if we were a tank turning our turret). Thing is the mob we're targeting isn't necessarily on our plane, it's an arbitrarily curved surface... but we want to know how much to turn the turret that it would face that mob. That's what mine is accomplishing. And from my interpretation of OPs post, that seemed like what they wanted, and is why I shared it. (mind you, that may not have been what they wanted either... but they never confirmed/denied).

    Hence my definition in my previous post:
     
    Last edited: Mar 3, 2020
  7. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,108
    Absolutely true. I forgot to document that as well, that the normal is considered as perpendicular to the common plane. It wasn't intended as a replacement for your solution, just an addendum for the general case.

    Maybe I should've clarified this when I wrote that it has one less Cross than yours. I certainly didn't mean yours was bad or redundant. Each has their own use.
     
  8. Volchok

    Volchok

    Joined:
    Jul 26, 2017
    Posts:
    127
    THANK YOU SO MUCH, MAN! YOU SAVED ME! EXCELLENT AND BEAUTIFUL SOLUTION! IT HELPED ME A LOT IN MY GAME! GOOD LUCK AND HAPPINESS TO YOU! :):):)
     
    Last edited: May 15, 2021
  9. Whatever560

    Whatever560

    Joined:
    Jan 5, 2016
    Posts:
    516