Search code examples
c#unit-testingunity-game-enginemath-functions

Unit Test Mathf.Sine C# / Unity


I am trying to write down a unit test to check if a new vector3 returns (cos(angle), 0, sin(angle)). So if pass angle = 0, it should returns (1,0,0).

I know it is something related to the Episilon, but I don't know how to formulate it.

Here is my code:

public Vector3 SetForceDirection(float angleInDegrees) 
    {
        float angleInRadians = angleInDegrees * Mathf.Deg2Rad;
        
        return new Vector3(Mathf.Cos(angleInRadians), 0, Mathf.Sin(angleInRadians));
    }

And my unit test:

void SetforceDirection_Angle0_Return_1_0_0() 
    {
        CtrlHorizontal _ctrlHorizontal = new CtrlHorizontal();

        Vector3 result = _ctrlHorizontal.SetForceDirection(0);

        Assert.That(result == new Vector3(1, 0, 0));
    }

Thanks!


Solution

  • Use Debug.Log to check what values Mathf.Sin()/Cos() return actually. The default precision used for Vector3.ToString() is 5 digits after the dot, iirc. To get higher precision in the debug logs, use

    Vector3 v = new Vector3(1.1234567891011, 0.00000001, 2.2222222222);
    Debug.Log(v.ToString("F10"));
    

    This should print the vector values with 10 digits after the dot.

    So, what's actually happening?
    Let's take for example the angle 90 degrees. The result of Mathf.Cos() will be:

    tmp1 = 90f * Mathf.Deg2Rad; // 1.570769
    tmp2 = Mathf.Cos(tmp1); // -4.371139E-08
    

    So, Mathf.Cos() returns -4.37139E-08 rather than 0. The reason is floating points precision limit. It breaks a lot of things in computer science.
    The results you get from Mathf.Sin()/Cos() with angles like 0, 90, 120 won't give you 0's and 1's, instead they will give you numbers like 4.371139E-08. This value is very very very close to zero but it's not a zero. So, when you compare such results to the new Vector 1, 0 and 0 in your code the equation fails.
    Although, the Vector3 == Vector3 operation in Unity already should apply some rounding so the values that are really close to each other will be treated as equal. As opposed to Vector3.Euals() which does the exact comparison.
    Here is the source code of the Vector3 == Vector3 operation from Unity's source code repo: https://github.com/Unity-Technologies/UnityCsReference/blob/master/Runtime/Export/Math/Vector3.cs

    // Returns true if the vectors are equal.
    public static bool operator==(Vector3 lhs, Vector3 rhs)
    {
        // Returns false in the presence of NaN values.
        float diff_x = lhs.x - rhs.x;
        float diff_y = lhs.y - rhs.y;
        float diff_z = lhs.z - rhs.z;
        float sqrmag = diff_x * diff_x + diff_y * diff_y + diff_z * diff_z;
        return sqrmag < kEpsilon * kEpsilon;
    }
    

    As you can see, it checks if the squared difference between the vectors values is less than kEpsilon * kEpsilon ( kEpsilon = 0.00001, so the squared value is 0.000000001 ).
    The tricky thing here is Mathf.Cos() of 90 degrees might return 0.00001, and the == operation will compare 0.00001 * 0.00001 < kEpsilon * kEpsilon and it will return false, because these values are the same actually, so the result of the operation

    Vector3 v1 = new Vector3(1, 0, 0); // your "checkout" vector
    Vector3 v2 = new Vector3(1.00001, 0, 0); // the result of Mathf.Cos()/Sin() in your code
    // in the Vector3 == Vector3 operation Unity does:
    float sqrmag = 0.00001 * 0.00001 + 0 * 0 + 0 * 0;
    return sqrmag < kEpsilon * kEpsilon;
    

    0.000001 * 0.000001 is not less than kEpsilon * kEpsilon so you get the result where the vectors are not the same.

    So, you'll need to compare the vectors in some other way. As an option, you could try Mathf.Approximately(float a, float b) ( https://docs.unity3d.com/ScriptReference/Mathf.Approximately.html ) to compare each x, y, z values of the vectors separately instead of using Vector3 == Vector3.