5
\$\begingroup\$

The function below works fine, when the timesteps of the physicsengine are short enough for the function to update often. It determinates when the ship(the object) needs to start decelerating to reach 0 angular velocity by the time it reaches the target rotation. (Always using maximum torque of the ship to turn)

However, I want to simulate this in a scenario with longer timesteps. Since my function checks for when to start decelerating each update, it will miss this moment when the timesteps are longer and move past the target rotation. The ship then starts swaying back and forth (obviously).

What I need is some way to determinate the torque I need to apply to the body for it to end up at that sweetspot where it needs to decelerate. That way the ship can start decelerating when the next update occurs and hopefully end up facing the target rotation with almost 0 angular velocity!

private void TurnTowardsTarget(Vector2 target, float deltaTime)
    {
        Vector2 delta = target - body.Position;
        float targetRot = (float)Math.Atan2(delta.Y, delta.X);
        float dRot = (float)Math.Atan2(Math.Sin(targetRot - body.Rotation),
            Math.Cos(targetRot - body.Rotation));
        float angularAcceleration = maxTurnForce / body.Inertia;
        float avgDecelerationVelocity = body.AngularVelocity / 2;
        float time = dRot / avgDecelerationVelocity;
        float decelerationRequired = body.AngularVelocity / time;

        //Target rotation is to the right (positive rotations are to the right)
        if (dRot > 0)
        {
            //If the ship is rotating to the right
            if (body.AngularVelocity >= 0)
            {
                //Should we start decelerating?
                if (decelerationRequired >= angularAcceleration)
                {
                    //Decelerate
                    TurnLeft();
                }
                else
                {
                    //Keep on accelerating towards the target
                    TurnRight();
                }
            }
            else
            {
                //The ship is rotating the wrong way, turn towards the target
                TurnRight();
            }
        }

        //Target rotation is to the left (negative rotations are to the left)
        if (dRot < 0)
        {
            if (body.AngularVelocity <= 0)
            {
                //Should we start decelerating?
                if (-decelerationRequired >= angularAcceleration)
                {
                    //Decelerate
                    TurnRight();
                }
                else
                {
                    //Keep on accelerating towards the target
                    TurnLeft();
                }
            }
            else
            {
                //The ship is rotating the wrong way, turn towards the target
                TurnLeft();
            }
        }
    }

I hope I made myself quite clear, all the TurnLeft() and TurnRight() do is to apply "maxTurnForce" in the corresponding direction. Please respond if you find a solution, my brain is melting (x.

/Kimmeh!

Edit:

This is how the code looks now after implementing Sam's suggestion, it slightly overshoots the target rotation though, no matter the update-rate. And jitters back and forth from the target rotation.

I've put Math.Abs() around the time-calculation, changed sign of decelerationDecelerationRequired and changed sign of angularAcceleration depending on dRot.

        Vector2 delta = turnToTarget - body.Position;
        float targetRot = (float)Math.Atan2(delta.Y, delta.X);
        float dRot = (float)Math.Atan2(Math.Sin(targetRot - body.Rotation),
            Math.Cos(targetRot - body.Rotation));
        float angularAcceleration = maxTorque * -Math.Sign(dRot) / body.Inertia;
        float avgDecelerationVelocity = body.AngularVelocity / 2;
        float time = Math.Abs(dRot / avgDecelerationVelocity);
        float decelerationRequired = -body.AngularVelocity / time;

        float torque = (decelerationRequired - angularAcceleration) * body.Inertia;
        torqueToApply = MathHelper.Clamp(torque, -maxTorque, maxTorque);

Just to clarify, I'm using Farseer physics engine, and I apply the torque of the body right before calling the step() function of farseer. The body has a slight angular damping, so it should actually rotate a bit less than expected.

I think it's because it now smoothly adjusts to the decelerationRequired. (say if the decelerationRequired == angularAcceleration, the change of torque would be 0, when it should be +-maxTorque in order to stop in time.

\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

The problem is that TurnLeft() and TurnRight() basically work as on/off switches instead of continuously. You need to simulate what happens when the force is applied only during a fraction of the timestep. I suggest adding an argument to these functions indicating the force to be used instead of maxTurnForce: at a reasonably small scale, applying half the force is equivalent to applying it for half the time.

Assuming your current computations are correct, you already know the value of that force: it is (decelerationRequired - angularAcceleration) * body.Inertia, which should be clamped between -maxTurnForce and maxTurnForce.

Another nice thing is that the sign of the force already indicates the direction. You can therefore merge TurnLeft() and TurnRight() into a single Turn(float Force) function.

\$\endgroup\$
2
\$\begingroup\$

I know this is a very old question, but I recently tried to solve this problem too. I found that modeling the problem as a damped harmonic oscillator proved to be a fairly elegant solution with nice results. Here is a javascript implementation using an imaginary physics engine:

/**
 * Applies a torque to the body to rotate it towards the angle.
 */
function rotate(body, targetAngle) {
    k = 3.0; // Spring constant. Higher means faster rotation.
    m = body.momentOfInertia;
    d = 1.0; // Damping coefficient. Lower than 1.0 will overshoot.
    c = -Math.sqrt(4 * m * k) * d;
    v = body.angularVelocity;
    x = angleDelta(body.angle, targetAngle);
    springForce = k * x;
    dampingForce = c * v;
    body.applyTorque(springForce + dampingForce);
}

/**
 * Modulo that handles negatives properly.
 */
function mod(a, b) {
    return ((a % b) + b) % b;
}

/**
 * Return the difference between angles, normalized to [-pi, pi].
 */
function angleDelta(a, b) {
    diff = b - a;
    return mod(diff + Math.PI, Math.PI * 2) - Math.PI
}

This is a solution to a slightly different problem than the original, but it's the solution I was looking for when I came across this question, so hopefully it will be of some use to somebody else who was also in my situation.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Answering old questions is fine, especially if you have something new to add like you do! Welcome :) \$\endgroup\$
    – Elva
    Oct 17, 2015 at 8:17
0
\$\begingroup\$

i allways use something like this:

        if (keyboardManager.IsKeyDown(Keys.W))
        {
            if (Math.Abs(_car.AngularVelocity) < 10f)
            {
                totalRotation = 0 -_car.Rotation;
                while (totalRotation < -MathHelper.Pi) totalRotation += MathHelper.TwoPi;
                while (totalRotation > MathHelper.Pi) totalRotation -= MathHelper.TwoPi;
                _car.ApplyAngularImpulse(totalRotation * 0.01f);
            }
        }

in this case i try to turn my car to the angle 0 (totalRotation = 0 -_car.Rotation;).

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .