Search code examples
mathfloating-pointunity-game-enginegame-physics

Potential floating point issue with cosine acceleration curve


I am using a cosine curve to apply a force on an object between the range [0, pi]. By my calculations, that should give me a sine curve for the velocity which, at t=pi/2 should have a velocity of 1.0f

However, for the simplest of examples, I get a top speed of 0.753.

Now if this is a floating point issue, that is fine, but that is a very significant error so I am having trouble accepting that it is (and if it is, why is there such a huge error computing these values).

Some code:

// the function that gives the force to apply (totalTime = pi, maxForce = 1.0 in this example)
return ((Mathf.Cos(time * (Mathf.PI / totalTime)) * maxForce));


// the engine stores this value and in the next fixed update applies it to the rigidbody
// the mass is 1 so isn't affecting the result
engine.ApplyAccelerateForce(applyingForce * ship.rigidbody2D.mass);

Update

There is no gravity being applied to the object, no other objects in the world for it to interact with and no drag. I'm also using a RigidBody2D so the object is only moving on the plane.

Update 2

Ok have tried a super simple example and I get the result I am expecting so there must be something in my code. Will update once I have isolated what is different.

For the record, super simple code:

float forceThisFrame;
float startTime;

// Use this for initialization
void Start () {
    forceThisFrame = 0.0f;
    startTime = Time.fixedTime;
}

// Update is called once per frame
void Update () {
    float time = Time.fixedTime - startTime;

    if(time <= Mathf.PI)
    {

        forceThisFrame = Mathf.Cos (time);

        if(time >= (Mathf.PI /2.0f)- 0.01f && time <= (Mathf.PI /2.0f) + 0.01f)
        {
            print ("Speed: " + rigidbody2D.velocity);
        }
    }
    else
    {
        forceThisFrame = 0.0f;
    }
}

void FixedUpdate()
{
    rigidbody2D.AddForce(forceThisFrame * Vector2.up);
}

Update 3

I have changed my original code to match the above example as near as I can (remaining differences listed below) and I still get the discrepancy.

Here are my results of velocity against time. Neither of them make sense to me, with a constant force of 1N, that should result in a linear velocity function v(t) = t but that isn't quite what is produced by either example.

Remaining differences:

  • The code that is "calculating" the force (now just returning 1) is being run via a non-unity DLL, though the code itself resides within a Unity DLL (can explain more but can't believe this is relevant!)
  • The behaviour that is applying the force to the rigid body is a separate behaviour.
  • One is moving a cube in an empty enviroment, the other is moving a Model3D and there is a plane nearby - tried a cube with same code in broken project, same problem

Other than that, I can't see any difference and I certainly can't see why any of those things would affect it. They both apply a force of 1 on an object every fixed update.


Solution

  • This was a multi-part problem that started with me not fully understanding Update vs. FixedUpdate in Unity, see this question on GameDev.SE for more info on that part.

    My "fix" from that was advancing a timer that went with the fixed update so as to not apply the force wrong. The problem, as demonstrated by Eric Postpischil was because the FixedUpdate, despite its name, is not called every 0.02s but instead at most every 0.02s. The fix for this was, in my update to apply some scaling to the force to apply to accomodate for missed fixed updates. My code ended up looking something like:

    Called From Update

    float oldTime = time;
    time = Time.fixedTime - startTime;
    float variableFixedDeltaTime = time - oldTime;
    float fixedRatio = variableFixedDeltaTime / Time.fixedDeltaTime;
    
    if(time <= totalTime)
    {
        applyingForce = forceFunction.GetValue(time) * fixedRatio;
    
        Vector2 currentVelocity = ship.rigidbody2D.velocity;
        Vector2 direction = new Vector2(ship.transform.right.x, ship.transform.right.y);
        float velocityAlongDir = Vector2.Dot(currentVelocity, direction);
        float velocityPrediction = velocityAlongDir + (applyingForce * lDeltaTime);
    
        if(time > 0.0f && // we are not interested if we are just starting
           ((velocityPrediction < 0.0f  && velocityAlongDir > 0.0f ) ||
           (velocityPrediction > 0.0f  && velocityAlongDir < 0.0f ) ))
        {
            float ratio = Mathf.Abs((velocityAlongDir / (applyingForce * lDeltaTime)));
            applyingForce = applyingForce * ratio;
    
            // We have reversed the direction so we must have arrived
            Deactivate();
        }
    
        engine.ApplyAccelerateForce(applyingForce);
    }
    

    Where ApplyAccelerateForce does:

    public void ApplyAccelerateForce(float requestedForce)
    {
        forceToApply += requestedForce;
    }
    

    Called from FixedUpdate

    rigidbody2D.AddForce(forceToApply * new Vector2(transform.right.x, transform.right.y));
    forceToApply = 0.0f;