A Question about Vector3.Angle

        Vector3 vector1 = new Vector3(1f, 1f, 1f);
        Vector3 vector2 = new Vector3(1f, 1f, 1f);
        Debug.Log("Angle between values: " + Vector3.Angle(vector1, vector2));

This keeps displaying 0.01978234 as opposed to what I’d expect, 0 (or at least a value much closer to zero).

I’m using Unity version 4.5.4f1

Am I doing something wrong, or is that an expected value?

Thanks!

That sure is weird.
Stops happening if you normalise the arguments

I guess I’ll take that as a lesson to always normalize my vectors before comparing the angle between them.

Thanks!

2 Likes

So this interested me… so I dug into it and I figured out why it’s happening.

So this is the implementation of Vector3.Angle (using a decompiler on the UnityEngine.dll)

    public static float Angle(Vector3 from, Vector3 to)
    {
      return Mathf.Acos(Mathf.Clamp(Vector3.Dot(from.normalized, to.normalized), -1f, 1f)) * 57.29578f;
    }

You’ll notice that it uses the normalized versions of the vectors. Which would at first lead you to think it should work correctly. But it doesn’t.

So I tried this:

        var v1 = new Vector3(1f, 1f, 1f);
        var v2 = new Vector3(1f, 1f, 1f);
        float a = Vector3.Dot(v1.normalized, v2.normalized);
        Debug.Log(a);

And sure enough 0.999999 came out as the result. Which doesn’t surprise me, a dot product is:

    public static float Dot(Vector3 lhs, Vector3 rhs)
    {
      return (float) ((double) lhs.x * (double) rhs.x + (double) lhs.y * (double) rhs.y + (double) lhs.z * (double) rhs.z);
    }

When you have a vector in <1,1,1> normalized you’re going to have <sqrt(3)/3, sqrt(3)/3, sqrt(3)/3>. Those irrational numbers there are going to be full of float error. So that float error resulting in a 0.0000000001 off is to be expected. And of course that tiny error turns weird after passing it through arccosine and converting to degrees.

So why does normalizing before passing it in works?

Well what happens there is you’re normalizing the vector, it just divides itself by its magnitude. But this is done as ‘double’ and ‘float’ for more precision (note more does not always mean better).

    public void Normalize()
    {
      float num = Vector3.Magnitude(this);
      if ((double) num > 9.99999974737875E-06)
        this = this / num;
      else
        this = Vector3.zero;
    }

    public static float Magnitude(Vector3 a)
    {
      return Mathf.Sqrt((float) ((double) a.x * (double) a.x + (double) a.y * (double) a.y + (double) a.z * (double) a.z));
    }

Well if you simulate this yourself:

        float a1 = Mathf.Sqrt(3f) / 3f;
        Debug.Log(a1);
        Debug.Log((double)a1);
        double b1 = (double)a1 * (double)a1;
        Debug.Log(b1 + b1 + b1);
        float l1 = Mathf.Sqrt((float)(b1 + b1 + b1));
        Debug.Log(l1);
        float a2 = a1 / l1;
        Debug.Log(a2);
        Debug.Log((double)a2);
        Debug.Log(a1 == a2);
        double b2 = (double)a2 * (double)a2;
        Debug.Log(b2 + b2 + b2);
        float l2 = Mathf.Sqrt((float)(b2 + b2 + b2));
        Debug.Log(l2);

Now first thing you’ll notice is that the printouts of ‘a’ as a float and as a double are different the first time:

Which is kind of to be expected. They are different data types.

You’ll also notice that when I sum the squares (before sqrt) we get that expected float error (note, sum of squares would be the same exact calculation as the dot product of 2 identical vectors):

Here’s where it gets interesting though.

Note the float value prints out the same, 0.5773503. But the doubles are different. And of course a1 and a2 don’t equal each other. It’s actually ever so slightly larger. This also goes to show that ‘ToString’ on a float doesn’t actually print out the precise value of a float… which makes sense because it’s binary converted to decimal and those irrational numbers are going to round off to something.

So the second time around we get the sum of the squares and instead of being ever so slightly below 1, it’s now ever so slightly above 1.

And as you know back in the Angle implementation, unity clamps the value between -1, and 1. So this value gets changed, unlike 0.999999 which is in range. And you accidentally get the correct answer.

6 Likes