игровое программирование box2d физики - ориентирование подобного башенке объекта с помощью крутящих моментов

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

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

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

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

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

Башенка имеет небольшую функцию AI, которая переоценивает ее ориентацию, чтобы проверить, что она указывает на правильное направление и активирует вращающее устройство. Это происходит каждый 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

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

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

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

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

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

9
задан kikito 11 April 2010 в 04:10
поделиться

5 ответов

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

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

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 теперь доступен здесь . Важная информация находится внутри агентов / AI.lua (файл .love можно открыть с помощью распаковщика zip)

1
ответ дан 3 November 2019 в 07:12
поделиться

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

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

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

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

EDIT:
Мое уравнение было неправильным, оно должно быть 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)
3
ответ дан 3 November 2019 в 07:12
поделиться

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

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

Могу быть совершенно неправильным, не делал ничего подобного в течение долгого времени. Наверное, более простое решение. Я предполагаю, что ускорение не линейное.

0
ответ дан 3 November 2019 в 07:12
поделиться

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

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

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 довольно мал, возможно, безумное ускорение никогда не проявляется. Благодаря максимальной угловой скорости вращения башни выглядят реалистично.

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

0
ответ дан 3 November 2019 в 07:12
поделиться

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

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

P = P0 * differenceAngle

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

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

РЕДАКТИРОВАТЬ:

Вот приложение, написанное с использованием Обработки , которое использует 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;
}
1
ответ дан 3 November 2019 в 07:12
поделиться
Другие вопросы по тегам:

Похожие вопросы: