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. Dismiss Notice

Quaternion.Dot().. what is supposed to return?

Discussion in 'Scripting' started by beatdesign, Nov 13, 2018.

  1. beatdesign

    beatdesign

    Joined:
    Apr 3, 2015
    Posts:
    137
    Hi,
    I need to know if two orientation are the same, are opposite, or are perpendicular. For that, I think that Quaternion.Dot(obj1.rotation, obj2.rotation) is the right choice.
    As far as I know, Quaternion.Dot() should return:
    * 1 if obj1.rotation == obj2.rotation
    * 0 if obj1.rotation and obj2.rotation are perpendicular
    * -1 if obj1.rotation and obj2.rotation are opposite

    Instead, I got:
    * 1 if obj1.rotation == obj2.rotation
    * about 0.7 if obj1.rotation and obj2.rotation are perpendicular
    * 0 if obj1.rotation and obj2.rotation are opposite

    ..am I missing something? Maybe Quaternion.Dot() behaviour is different from Vector3.Dot()?
     
  2. MSplitz-PsychoK

    MSplitz-PsychoK

    Joined:
    May 16, 2015
    Posts:
    1,278
    What you are expecting is correct... maybe you are mixing up transform.rotation and transform.localRotation?
     
  3. beatdesign

    beatdesign

    Joined:
    Apr 3, 2015
    Posts:
    137
    Nope. The Quaternion.Dot result is incorrect in both cases:
    * Quaternion.Dot(obj0.transform.rotation, obj1.transform.rotation)
    * Quaternion.Dot(obj0.transform.localRotation, obj1.transform.localRotation)
    ..They both return strange values I mentioned above.

    Am I missing something? :\
     
  4. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,575
    Check absolute rotation angles of both objects. See if they are the same, perpendicular, opposite.

    Or you may read wrong objects?
     
  5. lordofduct

    lordofduct

    Joined:
    Oct 3, 2011
    Posts:
    8,380
    So underneath, yes, Vector3.Dot and Quaternion.Dot are arithmetically similar.

    Here is Quternion.Dot:
    Code (csharp):
    1.  
    2.     /// <summary>
    3.     ///   <para>The dot product between two rotations.</para>
    4.     /// </summary>
    5.     /// <param name="a"></param>
    6.     /// <param name="b"></param>
    7.     public static float Dot(Quaternion a, Quaternion b)
    8.     {
    9.       return (float) ((double) a.x * (double) b.x + (double) a.y * (double) b.y + (double) a.z * (double) b.z + (double) a.w * (double) b.w);
    10.     }
    11.  
    And Vector3.Dot:
    Code (csharp):
    1.  
    2.     /// <summary>
    3.     ///   <para>Dot Product of two vectors.</para>
    4.     /// </summary>
    5.     /// <param name="lhs"></param>
    6.     /// <param name="rhs"></param>
    7.     public static float Dot(Vector3 lhs, Vector3 rhs)
    8.     {
    9.       return (float) ((double) lhs.x * (double) rhs.x + (double) lhs.y * (double) rhs.y + (double) lhs.z * (double) rhs.z);
    10.     }
    11.  
    Where they differ is what the results the dot product will create.

    2 unit rotational quaternions will always result in a dot product in the range 0->1, instead of -1->1.

    This is also why the formula for getting the angle between quats is:
    Code (csharp):
    1.  
    2.     /// <summary>
    3.     ///   <para>Returns the angle in degrees between two rotations a and b.</para>
    4.     /// </summary>
    5.     /// <param name="a"></param>
    6.     /// <param name="b"></param>
    7.     public static float Angle(Quaternion a, Quaternion b)
    8.     {
    9.       float num = Quaternion.Dot(a, b);
    10.       return !Quaternion.IsEqualUsingDot(num) ? (float) ((double) Mathf.Acos(Mathf.Min(Mathf.Abs(num), 1f)) * 2.0 * 57.2957801818848) : 0.0f;
    11.     }
    12.  
    Note they double the acos.
     
    hippocoder and Antypodish like this.
  6. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,575
    This is good point.
     
  7. beatdesign

    beatdesign

    Joined:
    Apr 3, 2015
    Posts:
    137
    Thank you for your reply.
    So, there is no way to know if 2 axis of rotation are perpendicular each other using Quaternion.Dot()?
    I mean: One of the use of the dot product is to know if 2 vectors are perpendicular, beacause Dot(A,B) == 0 if A, B Are perpendicular each other.
    What is the correct use of Quaternion.Dot() instead? What is it useful for?
     
  8. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,575
    Use vector dot product, to detect if vectors are perpendicular. You will get result -1 to 1. Where -1 is facing opposite direction.

    Rotation along such vector is ignored. Hence for that, if needed, you need quaternion.

    For quaternion, you will be expecting 1, if rotation is the same. For example you need to check, if both local forward and up/right vectors are same, or near the same, as the target rotation.
     
  9. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,917
    Vector3.Angle is usually the best choice. Vector3.Dot if you know math.

    The problem with Quaternion.Dot and Quaternion.Angle (besides the funny numbers) is they also count the spin: if you point both fingers off at the same angle, but tilt one sideways, still pointing the same way, Quaternion.Dot/Angle says they are different.

    The only reason Q.Dot is there is because Quaternions are standard and they always include Dot.
     
  10. Antypodish

    Antypodish

    Joined:
    Apr 29, 2014
    Posts:
    10,575
    Also, Quaternion.dot technically should be more efficient, than calculating two vectors for each object/reference, which are derived from quaternion itself anyway. For example transform forward vector is Quaternion * Vector.forward.
    Now if you need 4 vectors, so you got 4 calculations and then on top of that, getting dot (s) calculations.
    While quaternion.dot is more direct approach. That is, if you need check for same / similar rotations.

    However, unless doing thousands of such calculations per frame, these will not be noticeable.
    But good to know, when going into lower level programming. I.e. ECS.

    And for less familiar / simplicity / productivity, vectors dot(s) may be more convenient.
    + you can get opposite direction as -1.
     
  11. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    926
    I came across this when the dot product of two quaternions returned values for me that i couldn't follow.
    For Euler values of (-90, 0, 89.something) and (-90, 0, 90) their Quaternion.Dot() returns around -1 for me. When the 89.something is nudged to 90 it suddenly returns 1. I don't understand this as i would expect it to be similar to vectors, where the value is a gradient towards conforming directions. Is this an expected outcome nonetheless?
     
  12. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,525
    Well, even quaternions do have more than one representation for the same orientation. As you may know, a quaternion is essentially just a rotation vector (x,y,z) and how much to rotate around that rotation axis (w). So inverting the vector as well as reversing the rotation direction will result in the same orientation. So generally when doing p = -q then p and q still represent the same rotation but have a different numerical representation. That's also why, when you look at the implementation of Quaternion.Angle that @lordofduct quoted above, you will see that they used
    Mathf.Abs
    in order to get the difference between the two quaternions.

    Since a quaternion essentially does a double rotation, the angle "encoded" in the quaternion is actually half the angle. You may want to watch the 3b1b videos on quaternion and "double cover" if you wand to get a better understanding about quaternions.
    Quaternions essentially live in 4d space and are represented by a 4d complex number (1 real part which is w and 3 imaginary parts which are x,y and z).
    When you rotate a vector / point by a quaternion like
    p * q
    you actually calculate
    q * p * qi
    where qi is the complex conjugate of q. As you may know from complex numbers, multiplying a complex number with it's complex conjugate will make the result real again. For quaternions that means the first rotation actually rotates your point into 4d space and the second rotation with the conjugate rotates it back into 3d space. That's why the actual angle that the quaternion represents is half the total angle.

    If you really want to learn more, I recommend this Numberphile video (after that I also recommend the extra footage). James Grime explains all necessary basics and also how you can generate the 4 parts of a quaternion. Also the intro with complex numbers should give you a better idea about the whole point.
    This video also does explain the double rotation quite well.
     
    rango_ember and Flavelius like this.
  13. Flavelius

    Flavelius

    Joined:
    Jul 8, 2012
    Posts:
    926
    I saw the 3b1b video, before but couldn't really grasp it, watching the other videos revealed some interesting insights but didn't help my understanding of the problem. But what you wrote,
    "So inverting the vector as well as reversing the rotation direction will result in the same orientation" made sense to me.
    Even though i still struggle to understand quaternions, i think i atleast can use this small insight to chose a different approach for my case (for example ((angles/180)-0.5)*2 seems to be usable)
     
  14. emeraldy

    emeraldy

    Joined:
    Nov 26, 2015
    Posts:
    4
    Does quaternion dot product return cosine of the angle theta between the two quaternions or cosine of half theta? The code snippet above from lordofduct suggests cosine half theta but I have found other sources say cosine theta.
     
  15. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,525
    It would be half theta. Just think about a simple usecase. Assume one of the quaternions to be the identity rotation
    (0,0,0,1)
    and the other any other rotation like
    (0.5, 0.5, 0.5, 0.5)
    (which is the simplest example quaternion of a 120° rotation around the diagonal vector
    (1,1,1)
    ). Taking the dot product of those would yield the value 0.5f. The acos of that is 60° while the actual angle is 120°.

    A similar approach can be done with simple 90° rotations where we would have something like
    (0.707,0,0,0.707)
    . This is a 90° rotation around the x axis. (of course 0.707 is rounded, it's actually 1/sqrt(2)). The arc cosine of 0,7071067... would be 45°
     
    hippocoder and lordofduct like this.
  16. emeraldy

    emeraldy

    Joined:
    Nov 26, 2015
    Posts:
    4
    Thanks, bunny for the quick reply. I also hand-derived it with some actual quaternions and arrived at half theta and thought that I had missed something.
     
  17. Bunny83

    Bunny83

    Joined:
    Oct 18, 2010
    Posts:
    3,525
    Well, the angle a quaternion describes actually requires half the angle when you create it because it's applied twice to the vector as I explained in my spoiler above. I also can recommend the Numberphile video where he actually constructs a quaternion from a given normalized rotation vector as well as the angle around that vector. You actually have to take half the angle.

    Code (CSharp):
    1. x = axis.x * sin(angle / 2);
    2. y = axis.y * sin(angle / 2);
    3. z = axis.z * sin(angle / 2);
    4. w = cos(angle / 2);
    Here axis is a normalized direction vector which defines the axis of rotation while "angle" is the desired angle around that axis in radians.
     
  18. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    Sorry about resurrecting this, but it popped up and I have important inquiries.

    This appears to be a decompiled Quaternion implementation
    Code (csharp):
    1.     /// <summary>
    2.     ///   <para>Returns the angle in degrees between two rotations a and b.</para>
    3.     /// </summary>
    4.     /// <param name="a"></param>
    5.     /// <param name="b"></param>
    6.     public static float Angle(Quaternion a, Quaternion b)
    7.     {
    8.       float num = Quaternion.Dot(a, b);
    9.       return !Quaternion.IsEqualUsingDot(num) ? (float) ((double) Mathf.Acos(Mathf.Min(Mathf.Abs(num), 1f)) * 2.0 * 57.2957801818848) : 0.0f;
    10.     }
    I have two issues.
    On the first glance, this appears to be the same as the official implementation. However, it's not. Observe
    Code (csharp):
    1. public static float Angle(Quaternion a, Quaternion b)
    2.         {
    3.             float dot = Mathf.Min(Mathf.Abs(Dot(a, b)), 1.0F);
    4.             return IsEqualUsingDot(dot) ? 0.0f : Mathf.Acos(dot) * 2.0F * Mathf.Rad2Deg;
    5.         }
    Here, the doubling is outside Acos. No, it's the same, my bad. Decompiler made it look perplexing. Though, while we're at it, the implementations do differ in how they treat the value. In the first one,
    num
    is received by IsEqualUsingDot completely untreated!

    The second question is about Unity's code, as this dot product line (and I see it repeated elsewhere) implies that the dot product can be greater than 1. This is because min(n, 1) implies a mandatory upper bound of 1.

    Sure, one would think, the idea is to make IsEqualUsingDot reliable
    Code (csharp):
    1.         private static bool IsEqualUsingDot(float dot)
    2.         {
    3.             // Returns false in the presence of NaN values.
    4.             return dot > 1.0f - kEpsilon;
    5.         }
    In the same codebase, however, one can also see this
    Code (csharp):
    1.         public static bool operator==(Quaternion lhs, Quaternion rhs)
    2.         {
    3.             return IsEqualUsingDot(Dot(lhs, rhs));
    4.         }
    Clamping dot product makes zero sense in my mind. Even if the product does go beyond 1 (ever so slightly), doesn't clamping it violate the whole point of doing this?
     
  19. orionsyndrome

    orionsyndrome

    Joined:
    May 4, 2014
    Posts:
    3,043
    I have come to a conclusion that something is wrong with the implementation shown at
    https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Math/Quaternion.cs

    It was tampered, and it should be more in line what @lordofduct had shown above.
    Code (csharp):
    1. public static float Angle(Quaternion a, Quaternion b)
    2. {
    3.   float dot = Dot(a, b);
    4.   return IsEqualUsingDot(dot) ? 0f : Mathf.Acos(Mathf.Min(Mathf.Abs(dot), 1f)) * 2f * Mathf.Rad2Deg;
    5. }
    Clamping makes more sense when used exclusively in acos.