Spinning more than 360 deg and FP precision

Posting this since I know someone asked about stopping precisely at 360 degrees no matter the current starting rotation of the unit. Here’s how I solved it. I have a class member variable for spinAmountPerAction which is 360f.

Edit: I previously used Mathf.Epsilon to check for floating point rounding errors that could cause isActive to stay true indefinitely. But I didn’t think I used it correctly. The following edit is a better solution.

P.S. Yes, I know this is a dummy exercise but perhaps needing to do something exact would be useful in the future.

        float potentialSpinAmount = 360f * Time.deltaTime;
        float spinAddAmount = Mathf.Min(potentialSpinAmount, spinAmountPerAction - degreesSpunSoFar);
        transform.eulerAngles += new Vector3(0, spinAddAmount, 0);
        
        if ((degreesSpunSoFar + potentialSpinAmount) >= spinAmountPerAction) 
        {
            isActive = false;
            degreesSpunSoFar = 0;
        } else { 
            degreesSpunSoFar += spinAddAmount;
        }
2 Likes

Could the final check be simplified like this?

        float potentionalSpinAmount = 360f * Time.deltaTime;
        float spinAmount = Mathf.Min(potentionalSpinAmount, spinAmountPerAction - spinAmountDone);
        transform.eulerAngles += new Vector3(0, spinAmount, 0);

        spinAmountDone += spinAmount;
        if (spinAmountDone >= spinAmountPerAction)
        {
            isActive = false;
            spinAmountDone = 0f;
        }

It’s been a while since I looked at this code so my answer may be wrong.

My way looks more complex but I think the reason I did it that way was to protect against certain floating point rounding errors.

You could have the case where the result of the Mathf.Min operation + the previously spun amount approaches but never ever equals the spinAmountPerAction due to floating point math rounding error. So I just have extra guarding in place to protect against that outcome. It does this by forcing the “over spin” condition so to speak.

I looked up a few things and here’s another way to do it. I started with your code and used your variable names in this version. This might make it clearer what indeed is going on. The order of the operands in the || is designed to take advantage of short circuit evaluation.

        float potentionalSpinAmount = 360f * Time.deltaTime;
        float spinAmount = Mathf.Min(potentionalSpinAmount,
                                     spinAmountPerAction - spinAmountDone);
        transform.eulerAngles += new Vector3(0, spinAmount, 0);

        spinAmountDone += spinAmount;
        if (spinAmountDone >= spinAmountPerAction ||
            Mathf.Approximately(spinAmountDone, spinAmountPerAction ))
        {
            isActive = false;
            spinAmountDone = 0f;
        }

here’s another option using an early return but to be honest I’m not knowledgeable enough to know if this this avoids the floating point precision problem.

        float potentionalSpinAmount = 360f * Time.deltaTime;
        float spinAmount = Mathf.Min(potentionalSpinAmount,
                                     spinAmountPerAction - spinAmountDone);
        transform.eulerAngles += new Vector3(0, spinAmount, 0);

        spinAmountDone += spinAmount;
        //Warning - Use with caution - I don't know if this will have intended effect
        if (spinAmountDone < spinAmountPerAction) return;

        isActive = false;
        spinAmountDone = 0f;

heres my shot at dealing with ensuring you stop at exactly the same angle you start at

    private float totalSpinAmount;
    private float maxSpinAmount = 360f;
    private void Update()
    {
        if (!isActive)
        {
            return;
        }

        float spinAddAmount = maxSpinAmount * Time.deltaTime;
        // If we would spin more than 360 degrees
        if (totalSpinAmount + spinAddAmount >= maxSpinAmount)
        {
            // Get the remainder between the total and the max
            // And use that to spin
            spinAddAmount = maxSpinAmount - totalSpinAmount;
            isActive = false;
        }
        else
        {
            totalSpinAmount += spinAddAmount;
        }
        transform.eulerAngles += new Vector3(0, spinAddAmount, 0);
        
    }
    public void Spin()
    {
        totalSpinAmount = 0;
        isActive = true;
    }

Privacy & Terms