la programación de juegos de física Box2D - orientación de un objeto torreta como el uso de pares

StackOverflow https://stackoverflow.com/questions/2617476

  •  26-09-2019
  •  | 
  •  

Pregunta

Este es un problema golpeo cuando se trata de poner en práctica un juego usando el motor LÖVE , que cubre box2d con Lua scripting.

El objetivo es simple:. Una torreta-como objeto (visto desde la parte superior, en un entorno 2D) necesita orientarse para que apunte a un objetivo

La torreta está en la coordenadas x, y, y el objetivo es en tx, ty. Podemos considerar que x, y son fijos, pero tx, ty tienden a variar de un instante a otro (es decir, que serían el cursor del ratón).

La torreta tiene un rotor que puede aplicar una fuerza de rotación (par) en cualquier momento dado, en sentido horario o en sentido antihorario. La magnitud de esa fuerza tiene un límite superior llamado maxTorque.

La torreta también tiene cierta inercia de rotación, que actúa para el movimiento angular de la misma masa manera actúa para el movimiento lineal. No hay fricción de ningún tipo, por lo que la torreta seguir girando si tiene una velocidad angular.

La torreta tiene una pequeña función de AI que reevalúa su orientación para verificar que apunte a la dirección correcta, y activa el rotador. Esto ocurre cada dt (~ 60 veces por segundo). Parece que este en este momento:

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

... falla. Me explico con dos situaciones ilustrativas:

  • La torreta "oscila" en todo el targetAngle.
  • Si el objetivo es "justo detrás de la torreta, sólo un poco hacia la derecha", la torreta se pondrá en marcha la aplicación de pares de giro hacia la derecha, y mantener su aplicación hasta el instante en que se supera el ángulo del objetivo. En ese momento se iniciará la aplicación de pares de giro en la dirección opuesta. Pero habrá ganado una velocidad angular significativa, por lo que se mantendrá hacia la derecha pasando por un tiempo ... hasta que el objetivo será "justo detrás, pero un poco hacia la izquierda". Y comenzará de nuevo. Así que la torreta oscilará o incluso ir en círculos redondos.

Creo que mi torreta debe empezar a aplicar pares en el "sentido contrario del camino más corto" antes de que llegue el ángulo del objetivo (como un coche de frenado antes de parar).

Intuitivamente, creo que la torreta debe "empezar a aplicar pares en la dirección opuesta de la ruta más corta cuando está a mitad de camino a la meta objetiva". Mi intuición me dice que tiene algo que ver con la velocidad angular. Y luego está el hecho de que el objetivo es móvil -. No sé si debería tenerlo en cuenta de alguna manera o simplemente ignorarlo

¿Cómo se calcula cuando la torreta debe "iniciar el frenado"?

¿Fue útil?

Solución 3

Ok creo que tengo la solución.

Esto se basa en la idea de Beta, pero con algunos ajustes necesarios. Aquí va:

local twoPi = 2.0 * math.pi -- small optimisation 

-- returns -1, 1 or 0 depending on whether x>0, x<0 or x=0
function _sign(x)
  return x>0 and 1 or x<0 and -1 or 0
end

-- transforms any angle so it is on the 0-2Pi range
local _normalizeAngle = function(angle)
  angle = angle % twoPi
  return (angle < 0 and (angle + twoPi) or angle)
end

function Turret:update(dt)

  local tx, ty = self:getTargetPosition()
  local x, y = self:getPosition()
  local angle = self:getAngle()
  local maxTorque = self:getMaxTorque()
  local inertia = self:getInertia()
  local w = self:getAngularVelocity()

  local targetAngle = math.atan2(ty-y,tx-x)

  -- distance I have to cover
  local differenceAngle = _normalizeAngle(targetAngle - angle)

  -- distance it will take me to stop
  local brakingAngle = _normalizeAngle(_sign(w)*2.0*w*w*inertia/maxTorque)

  local torque = maxTorque

  -- two of these 3 conditions must be true
  local a,b,c = differenceAngle > math.pi, brakingAngle > differenceAngle, w > 0
  if( (a and b) or (a and c) or (b and c) ) then
    torque = -torque
  end

  self:applyTorque(torque)
end

El concepto detrás de esto es simple: Tengo que calcular la cantidad de "espacio" (ángulo) las necesidades de la torreta con el fin de detener por completo. Eso depende de lo rápido que se mueve la torreta y la cantidad de par motor puede aplicarse a sí mismo. En pocas palabras, eso es lo que calculo con brakingAngle.

Mi fórmula para calcular este ángulo es ligeramente diferente de la Beta de. Un amigo mío me ayudó con la física, y bien, que parece estar funcionando. Añadiendo el signo de W era mi idea.

he tenido que implementar una función de "normalización", que pone a cualquier ángulo del respaldo a la zona 0-2Pi.

Inicialmente se trataba de una enredada if-else-if-else. Puesto que las condiciones en las que muy repetitiva, he utilizado algunos lógica booleana el fin de simplificar el algoritmo. La desventaja es que, aunque funciona bien y no es complicado, no transpiran por qué funciona.

Una vez que el código es un poco más depurado Voy a publicar un enlace a una demo aquí.

Muchas gracias.

EDITAR: El amor de Trabajo de la muestra ya está disponible aquí . Lo importante es el interior de los actores / AI.lua (el archivo .Love se puede abrir con un descompresor ZIP)

Otros consejos

Piense hacia atrás. La torreta debe "iniciar el frenado" cuando tiene espacio suficiente para desacelerar desde su actual velocidad angular a un punto muerto, lo que es lo mismo que la habitación que necesitaría para acelerar de un punto muerto a su velocidad angular actual, que es

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

También puede tener algunos problemas con pequeñas oscilaciones en torno al objetivo si su tiempo de paso es demasiado grande; que va a requerir un poco más de finura, usted tiene que frenar un poco antes, y más suavemente. No se preocupe por eso hasta que lo vea.

Eso debe ser lo suficientemente bueno por ahora, pero hay otra captura que pueden tropezar más adelante: decidir qué camino tomar. A veces va el camino más largo es más rápido, si vas de esa manera ya. En ese caso, usted tiene que decidir qué camino toma menos tiempo, lo que no es difícil, pero una vez más, cruzar ese puente cuando llegue a él.

editar
Mi ecuación estaba mal, debe ser Inercia / 2 * maxTorque, no maxTorque 2 * / Inercia (Eso es lo que me pasa por tratar de hacer el álgebra en el teclado). Lo he arreglado.

Prueba esto:

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)

Este parece ser un problema que puede ser resuelto con un controlador PID . Los utilizo en mi trabajo para controlar una potencia de calefacción para establecer una temperatura.

Para el componente 'P', se aplica un par de torsión que es proporcional a la diferencia entre el ángulo de torreta y el es decir ángulo del objetivo.

P = P0 * differenceAngle

Si esto sigue oscilando demasiado (que será un poco) a continuación, añadir un componente 'i',

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

Si este rebasamiento demasiado, entonces añadir un término 'D'

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

P0, I0 y D0 son constantes que se pueden ajustar para obtener el comportamiento que desea (es decir, la rapidez con las torretas responden etc.)

Así como una punta, normalmente P0> I0> D0

Usar estos términos para determinar la cantidad de par de torsión se aplica es decir

magnitudeAngMomentum = P + I + D

EDIT:

Esta es una aplicación escrita utilizando Procesamiento que los usos PID. En realidad funciona bien sin I o D. ver su funcionamiento aquí


// 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;
}

pudo encontrar una ecuación para la velocidad angular vs distancia angular para el rotor cuando se aplica par de aceleración, y encontrar la misma ecuación para cuando se aplica el par de frenado.

A continuación, modifique la ecuación de rotura de tal manera que el eje intesects distancia angular en el ángulo requerido. Con estas dos ecuaciones se puede calcular la distancia angular en la que se cruzan la que se le dará el punto de ruptura.

Podría ser totalmente equivocado, sin embargo, no se ha hecho ninguna así durante mucho tiempo. Probablemente una solución más simple. Estoy suponiendo que la aceleración no es lineal.

Una versión simplificada de este problema es bastante simple de resolver. Supongamos que el motor tiene par infinito, es decir, puede cambiar la velocidad instantánea. Esto obviamente no es físicamente exactos, pero hace que el problema mucho más sencillo de resolver y que al final no es un problema.

Focus a una velocidad angular de destino no un ángulo del objetivo.

current_angle = "the turrets current angle";
target_angle = "the angle the turret should be pointing";
dt = "the timestep used for Box2D, usually 1/60";
max_omega = "the maximum speed a turret can rotate";

theta_delta = target_angle - current_angle;
normalized_delta = normalize theta_delta between -pi and pi;
delta_omega = normalized_deta / dt;
normalized_delta_omega = min( delta_omega, max_omega );

turret.SetAngularVelocity( normalized_delta_omega );

La razón por la que esto funciona es la torreta automáticamente intenta moverse más lento a medida que alcanza su ángulo objetivo.

El par infinita está enmascarado por el hecho de que la torreta no trata de cerrar la distancia instantánea. En su lugar, trata de cerrar la distancia en un solo paso de tiempo. Asimismo, puesto que el rango de PI a pi es bastante pequeña, posiblemente, las aceleraciones dementes nunca muestran a sí mismos. La velocidad angular máxima a mantener la rotación de la torreta de aspecto realista.

Nunca he trabajado a cabo la ecuación real para resolver con un par motor en lugar de la velocidad angular, pero me imagino que se parecen mucho a las ecuaciones PID.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top