программирование физической игры box2d - ориентация объекта, похожего на башню, с использованием крутящих моментов

Это проблема, с которой я столкнулся при попытке реализовать игру с использованием движка LÖVE, который охватывает box2d со сценариями Lua.

Цель проста: объект, похожий на башню (вид сверху, в 2D-окружении), должен сориентироваться так, чтобы он указывал на цель.

Турель находится в координатах x,y, а цель – в tx, ty. Мы можем считать, что x, y фиксированы, но tx, ty имеют тенденцию меняться от одного момента к другому (т. е. они будут курсором мыши).

Башня имеет ротор, который может прикладывать вращательную силу (крутящий момент) в любой момент по часовой стрелке или против часовой стрелки. Величина этой силы имеет верхний предел, называемый maxTorque.

Башня также имеет определенную инерцию вращения, которая действует для углового движения так же, как масса действует для линейного движения. Никакого трения нет, поэтому турель будет продолжать вращаться, если у нее есть угловая скорость.

У башни есть небольшая функция ИИ, которая переоценивает ее ориентацию, чтобы убедиться, что она указывает правильное направление, и активирует поворотное устройство. Это происходит каждые dt (~ 60 раз в секунду). Это выглядит так прямо сейчас:

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

... это не удается. Позвольте мне объяснить с двумя иллюстративными ситуациями:

  • Башня «колеблется» вокруг целевого угла.
  • Если цель находится «прямо за турелью, чуть по часовой стрелке», турель начнет применять крутящие моменты по часовой стрелке и будет продолжать применять их до момента, когда она превысит угол цели. В этот момент он начнет прикладывать крутящие моменты в противоположном направлении. Но он приобретет значительную угловую скорость, поэтому какое-то время будет двигаться по часовой стрелке... пока цель не окажется "сразу позади, но немного против часовой стрелки". И снова начнется. Таким образом, башня будет колебаться или даже ходить по кругу.

Я думаю, что моя башня должна начать применять крутящий момент в «противоположном направлении кратчайшего пути» до того, как она достигнет заданного угла (как автомобиль, тормозящий перед остановкой).

Интуитивно я думаю, что турель должна «начинать применять крутящий момент в направлении, противоположном кратчайшему пути, когда она находится примерно на полпути к цели». Моя интуиция подсказывает мне, что это как-то связано с угловой скоростью. А еще есть тот факт, что цель подвижна - я не знаю, следует ли мне это как-то учитывать или просто игнорировать.

Как рассчитать, когда башня должна "начать торможение"?


person kikito    schedule 11.04.2010    source источник


Ответы (5)


Думайте в обратном направлении. Башня должна «начать торможение», когда у нее будет достаточно места для замедления от текущей угловой скорости до полной остановки, что соответствует комнате, которая потребуется ей для ускорения от полной остановки до текущей угловой скорости, т. е.

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

У вас также могут возникнуть проблемы с небольшими колебаниями вокруг цели, если время вашего шага слишком велико; это потребует большей ловкости, вам придется тормозить чуть раньше и мягче. Не беспокойся об этом, пока не увидишь.

На данный момент этого должно быть достаточно, но есть еще одна загвоздка, которая может сбить вас с толку позже: решить, в какую сторону идти. Иногда пройти длинный путь быстрее, если вы уже идете этим путем. В этом случае вы должны решить, какой путь займет меньше времени, что нетрудно, но опять же, пересечь этот мост, когда вы придете к нему.

РЕДАКТИРОВАТЬ:
Мое уравнение было неправильным, оно должно быть Inertia/2*maxTorque, а не 2*maxTorque/Inertia (это то, что я получаю, пытаясь выполнить алгебру на клавиатуре). Я исправил это.

Попробуй это:

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)
person Beta    schedule 11.04.2010
comment
Привет Бета, спасибо за ответ. Я должен был упомянуть, что мои математические способности не очень сильны. Что мне делать с этим уравнением? Что-то кажется неявным для вас, но я просто не вижу этого. - person kikito; 11.04.2010
comment
мм я думал об этом больше. Согласно моим скромным попыткам, угол = MaxTorque * t * t / инерция для заданного периода времени t. Как вы переходите от этого уравнения к тому, которое вы показываете на своем примере, ускользает от меня. - person kikito; 11.04.2010
comment
Вы почти поняли: ускорение alpha=maxTorque/inertia, w=alphat, но для вычисления угла необходимо использовать среднюю скорость за период времени, поэтому angle=(maxTorquet/Inertia) *т/2. Теперь возьмите w=alphat и возведите его в квадрат: ww=alphaalphatt, и используйте это, чтобы избавиться от tt: angle = альфаtt/2 = альфа*(ww/альфаальфа)/2 = ww/2*альфа = ww* Инерция/2*макс.крутящий момент. - person Beta; 12.04.2010
comment
Я считаю, что формула может быть неправильной. Тем не менее, это привело меня на правильный путь. +1 за это! Вы упустили тот факт, что торможение происходит только в некоторых случаях, в зависимости от того, является ли значение w положительным или отрицательным. Смотрите мой ответ для полного объяснения. - person kikito; 14.04.2010

Похоже, что эту проблему можно решить с помощью ПИД-регулятора. Я использую их в своей работе для управления мощностью нагревателя, чтобы установить температуру.

Для компонента «P» вы прикладываете крутящий момент, пропорциональный разнице между углом башни и углом цели, т.е.

P = P0 * differenceAngle

Если это все еще слишком сильно колеблется (будет немного), тогда добавьте компонент «Я»,

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

Если это слишком много, добавьте термин «D»

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

P0, I0 и D0 — это константы, которые вы можете настроить, чтобы получить желаемое поведение (например, как быстро реагируют турели и т. д.).

Как совет, обычно P0 > I0 > D0

Используйте эти термины, чтобы определить, какой крутящий момент применяется, т.е.

magnitudeAngMomentum = P + I + D

ИЗМЕНИТЬ:

Вот приложение, написанное с использованием Processing, использующего PID. На самом деле он отлично работает без I или D. Посмотрите, как это работает здесь


// 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;
}
person Brendan    schedule 11.04.2010
comment
Проблема с этим подходом в том, что он предназначен для таких вещей, как системы отопления, где вы контролируете мощность, которая является первой производной от температуры; egarcia контролирует крутящий момент, который является вторым. P будет сильно промахиваться, потому что он нацелен на a = 0, а не на w = 0, I не помогает с колебанием, D может работать, но это сделает процесс МЕДЛЕННЫМ. - person Beta; 11.04.2010
comment
Вы правы, пример, который я привожу, напрямую не связан с крутящим моментом. Однако maxAngleMomentum пропорционально maxTorque, если учесть трение в "механизме" вращения башни - их можно считать взаимозаменяемыми при использовании произвольных единиц. - person Brendan; 12.04.2010
comment
Реализация выглядит нормально. Интересна идея сохранения углового момента. Однако это не то, о чем я просил - я хотел применить крутящие моменты, а в конце вы сами устанавливаете угол. Но +1 за создание демо и элегантность кода. - person kikito; 14.04.2010

Хорошо, я считаю, что получил решение.

Это основано на идее Беты, но с некоторыми необходимыми изменениями. Вот оно:

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

Идея, лежащая в основе этого, проста: мне нужно рассчитать, сколько «пространства» (угла) нужно турели, чтобы полностью остановиться. Это зависит от того, насколько быстро движется башня и какой крутящий момент она может приложить к себе. В двух словах, это то, что я вычисляю с помощью brakingAngle.

Моя формула для вычисления этого угла немного отличается от формулы Беты. Мой друг помог мне с физикой, и, кажется, они работают. Добавление знака w было моей идеей.

Мне пришлось реализовать функцию «нормализации», которая возвращает любой угол в зону 0-2Pi.

Первоначально это было запутанное if-else-if-else. Поскольку условия были очень повторяющимися, я использовал некоторую логическую логику, чтобы упростить алгоритм. Минус в том, что даже если работает нормально и не сложно, не понятно почему работает.

Как только код немного очистится, я опубликую здесь ссылку на демонстрацию.

Большое спасибо.

РЕДАКТИРОВАТЬ: Рабочий пример LÖVE теперь доступен здесь. Важные вещи находятся внутри Actors/AI.lua (файл .love можно открыть с помощью zip-распаковщика)

person kikito    schedule 14.04.2010

Вы можете найти уравнение для зависимости угловой скорости от углового расстояния для ротора при приложении ускоряющего крутящего момента и найти такое же уравнение для применения тормозного крутящего момента.

Затем измените уравнение разрыва таким образом, чтобы оно пересекало ось углового расстояния под требуемым углом. С помощью этих двух уравнений вы можете рассчитать угловое расстояние, на котором они пересекаются, что даст вам точку разрыва.

Хотя может и ошибся, давно такого не было. Возможно более простое решение. Я предполагаю, что ускорение не является линейным.

person Paul Creasey    schedule 11.04.2010

Упрощенную версию этой задачи решить довольно просто. Предположим, что двигатель имеет бесконечный крутящий момент, т.е. он может изменять скорость мгновенно. Это, очевидно, не является физически точным, но значительно упрощает решение проблемы и, в конце концов, не является проблемой.

Сосредоточьтесь на целевой угловой скорости, а не на целевом угле.

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 );

Причина, по которой это работает, заключается в том, что турель автоматически пытается двигаться медленнее, когда достигает заданного угла.

Бесконечный крутящий момент маскируется тем фактом, что башня не пытается мгновенно сократить дистанцию. Вместо этого он пытается сократить расстояние за один временной шаг. Кроме того, поскольку диапазон от -pi до pi довольно мал, возможно, безумные ускорения никогда не проявляются. Максимальная угловая скорость делает вращение башни реалистичным.

Я никогда не разрабатывал реальное уравнение для решения с крутящим моментом вместо угловой скорости, но я думаю, что оно будет очень похоже на уравнения ПИД.

person deft_code    schedule 12.04.2010
comment
Мне нравится ваша идея, и я буду иметь ее в виду на будущее. Спасибо, что поделились. Если вам интересно, мне удалось найти уравнение и запрограммировать рабочий пример. - person kikito; 14.04.2010