Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

physics game programming box2d - orientating a turret-like object using torques

Tags:

physics

lua

box2d

This is a problem I hit when trying to implement a game using the LÖVE engine, which covers box2d with Lua scripting.

The objective is simple: A turret-like object (seen from the top, on a 2D environment) needs to orientate itself so it points to a target.

The turret is on the x,y coordinates, and the target is on tx, ty. We can consider that x,y are fixed, but tx, ty tend to vary from one instant to the other (i.e. they would be the mouse cursor).

The turret has a rotor that can apply a rotational force (torque) on any given moment, clockwise or counter-clockwise. The magnitude of that force has an upper limit called maxTorque.

The turret also has certain rotational inertia, which acts for angular movement the same way mass acts for linear movement. There's no friction of any kind, so the turret will keep spinning if it has an angular velocity.

The turret has a small AI function that re-evaluates its orientation to verify that it points to the right direction, and activates the rotator. This happens every dt (~60 times per second). It looks like this right now:

function Turret:update(dt)
  local x,y = self:getPositon()
  local tx,ty = self:getTarget()
  local maxTorque = self:getMaxTorque() -- max force of the turret rotor
  local inertia = self:getInertia() -- the rotational inertia
  local w = self:getAngularVelocity() -- current angular velocity of the turret
  local angle = self:getAngle() -- the angle the turret is facing currently

  -- the angle of the like that links the turret center with the target
  local targetAngle = math.atan2(oy-y,ox-x)

  local differenceAngle = _normalizeAngle(targetAngle - angle)

  if(differenceAngle <= math.pi) then -- counter-clockwise is the shortest path
    self:applyTorque(maxTorque)
  else -- clockwise is the shortest path
    self:applyTorque(-maxTorque)
  end
end

... it fails. Let me explain with two illustrative situations:

  • The turret "oscillates" around the targetAngle.
  • If the target is "right behind the turret, just a little clock-wise", the turret will start applying clockwise torques, and keep applying them until the instant in which it surpasses the target angle. At that moment it will start applying torques on the opposite direction. But it will have gained a significant angular velocity, so it will keep going clockwise for some time... until the target will be "just behind, but a bit counter-clockwise". And it will start again. So the turret will oscillate or even go in round circles.

I think that my turret should start applying torques in the "opposite direction of the shortest path" before it reaches the target angle (like a car braking before stopping).

Intuitively, I think the turret should "start applying torques on the opposite direction of the shortest path when it is about half-way to the target objective". My intuition tells me that it has something to do with the angular velocity. And then there's the fact that the target is mobile - I don't know if I should take that into account somehow or just ignore it.

How do I calculate when the turret must "start braking"?

like image 367
kikito Avatar asked Apr 11 '10 15:04

kikito


2 Answers

Think backwards. The turret must "start braking" when it has just enough room to decelerate from its current angular velocity to a dead stop, which is the same as the room it would need to accelerate from a dead stop to its current angular velocity, which is

|differenceAngle| = w^2*Inertia/2*MaxTorque.

You may also have some trouble with small oscillations around the target if your step time is too large; that'll require a little more finesse, you'll have to brake a little sooner, and more gently. Don't worry about that until you see it.

That should be good enough for now, but there's another catch that may trip you up later: deciding which way to go. Sometimes going the long way around is quicker, if you're going that way already. In that case you have to decide which way takes less time, which is not difficult, but again, cross that bridge when you come to it.

EDIT:
My equation was wrong, it should be Inertia/2*maxTorque, not 2*maxTorque/Inertia (that's what I get for trying to do algebra at the keyboard). I've fixed it.

Try this:

local torque = maxTorque;
if(differenceAngle > math.pi) then -- clockwise is the shortest path
    torque = -torque;
end
if(differenceAngle < w*w*Inertia/(2*MaxTorque)) then -- brake
    torque = -torque;
end
self:applyTorque(torque)
like image 55
Beta Avatar answered Nov 19 '22 06:11

Beta


This seems like a problem that can be solved with a PID controller. I use them in my work to control a heater output to set a temperature.

For the 'P' component, you apply a torque that is proportional to the difference between the turret angle and the target angle i.e.

P = P0 * differenceAngle

If this still oscillates too much (it will a bit) then add an 'I' component,

integAngle = integAngle + differenceAngle * dt
I = I0 * integAngle

If this overshoots too much then add a 'D' term

derivAngle = (prevDifferenceAngle - differenceAngle) / dt
prevDifferenceAngle = differenceAngle
D = D0 * derivAngle

P0, I0 and D0 are constants that you can tune to get the behaviour that you want (i.e. how fast the turrets respond etc.)

Just as a tip, normally P0 > I0 > D0

Use these terms to determine how much torque is applied i.e.

magnitudeAngMomentum = P + I + D

EDIT:

Here is an application written using Processing that uses PID. It actually works fine without I or D. See it working here


// Demonstration of the use of PID algorithm to 
// simulate a turret finding a target. The mouse pointer is the target

float dt = 1e-2;
float turretAngle = 0.0;
float turretMass = 1;
// Tune these to get different turret behaviour
float P0 = 5.0;
float I0 = 0.0;
float D0 = 0.0;
float maxAngMomentum = 1.0;

void setup() {
  size(500, 500);  
  frameRate(1/dt);
}

void draw() {
  background(0);
  translate(width/2, height/2);

  float angVel, angMomentum, P, I, D, diffAngle, derivDiffAngle;
  float prevDiffAngle = 0.0;
  float integDiffAngle = 0.0;

  // Find the target
  float targetX = mouseX;
  float targetY = mouseY;  
  float targetAngle = atan2(targetY - 250, targetX - 250);

  diffAngle = targetAngle - turretAngle;
  integDiffAngle = integDiffAngle + diffAngle * dt;
  derivDiffAngle = (prevDiffAngle - diffAngle) / dt;

  P = P0 * diffAngle;
  I = I0 * integDiffAngle;
  D = D0 * derivDiffAngle;

  angMomentum = P + I + D;

  // This is the 'maxTorque' equivelant
  angMomentum = constrain(angMomentum, -maxAngMomentum, maxAngMomentum);

  // Ang. Momentum = mass * ang. velocity
  // ang. velocity = ang. momentum / mass
  angVel = angMomentum / turretMass;

  turretAngle = turretAngle + angVel * dt;

  // Draw the 'turret'
  rotate(turretAngle);
  triangle(-20, 10, -20, -10, 20, 0);

  prevDiffAngle = diffAngle;
}
like image 1
Brendan Avatar answered Nov 19 '22 05:11

Brendan