Как заставить компьютер реализовать 360 градусов = 0 градусов, вращая револьверную пушку - PullRequest
19 голосов
/ 26 июня 2009

Я делаю игру, и в ней есть орудийная башня с компьютерным управлением. Орудийная башня может вращаться на 360 градусов.

Он использует триггер, чтобы определить угол, который необходим для прицеливания пистолета (objdeg), и текущий угол наклона пистолета сохраняется в (gundeg)

следующий код вращает пистолет с заданной скоростью

if (objdeg > gundeg)
{
    gundeg++;
}
if (objdeg < gundeg)
{
    gundeg--;
}

Проблема в том, что если объект находится под углом 10 градусов, пистолет вращается, стреляет и уничтожает его, если другая цель появляется на 320 градусов, пистолет будет поворачиваться на 310 градусов против часовой стрелки, а не просто поворачиваться на 60 градусов по часовой стрелке, чтобы поразить его. .

Как я могу исправить свой код, чтобы он не работал глупо?

Ответы [ 14 ]

23 голосов
/ 26 июня 2009

Вы можете полностью избежать деления (и мода), если вы представляете свои углы в чем-то, что называется «BAMS», что означает «Система измерения двоичных углов». Идея состоит в том, что если вы храните ваши углы в N-битном целом числе, вы используете весь диапазон этого целого числа для представления угла. Таким образом, нет необходимости беспокоиться о переполнении после 360, потому что естественные свойства modulo-2 ^ N вашего представления позаботятся об этом за вас.

Например, допустим, вы используете 8 бит. Это разрезает ваш круг на 256 возможных ориентаций. (Вы можете выбрать больше битов, но для примера удобно 8). Пусть 0x00 означает 0 градусов, 0x40 означает 90 градусов, 0x80 - 180 градусов, а 0xC0 - 270 градусов. Не беспокойтесь о знаке, опять же, BAMS является естественным для углов. Если вы интерпретируете 0xC0 как «без знака» и масштабируете до 360/256 градусов на счет, ваш угол равен (+192) (360/256) = +270; но если вы интерпретируете 0xC0 как «со знаком», ваш угол равен (-64) (360/256) = -90. Обратите внимание, что -90 и +270 означают одно и то же в угловых единицах.

Если вы хотите применить функции триггера к углам BAMS, вы можете предварительно вычислить таблицы. Есть хитрости, чтобы уменьшить размеры таблиц, но вы можете видеть, что таблицы не такие большие. Для хранения всей таблицы значений синуса и косинуса с двойной точностью для 8-разрядных BAMS не требуется более 4 КБ памяти, что является курятиной в современных условиях.

Поскольку вы упомянули об использовании этого в игре, вы, вероятно, могли бы избежать использования 8-битных или 10-битных представлений. Каждый раз, когда вы добавляете или вычитаете углы, вы можете преобразовать результат в N битов, используя логическую операцию И, например, угол & = 0x00FF для 8 битов.

ЗАБЫЛ ЛУЧШУЮ ЧАСТЬ (правка)

Проблема поворота вправо против поворота влево легко решается в системе BAMS. Просто возьмите разницу и сохраните только N значащих битов. Интерпретация MSB как знакового бита указывает, в какую сторону вам следует повернуть. Если разница отрицательна, поверните противоположный путь с помощью abs () разницы.

Эта уродливая маленькая программа на C демонстрирует. Попробуйте сначала ввести его как 20 10 и 20 30. Затем попробуйте обмануть его, обернув вокруг нулевой точки. Дайте ему 20 -10, он повернет налево. Дайте ему 20 350, он все еще поворачивает налево. Обратите внимание, что поскольку он выполнен в 8 битах, то 181 неотличим от 180, поэтому не удивляйтесь, если вы подадите ему 20 201, и он поворачивается направо, а не налево - в разрешении, полученном с помощью восьми бит, поверните налево и поверните вправо этот случай одинаков. Положите 20 205, и он пойдет по более короткому пути.

#include <stdio.h>
#include <math.h>

#define TOBAMS(x)    (((x)/360.0) * 256)
#define TODEGS(b)    (((b)/256.0) * 360)

int main(void)
{
    double a1, a2;     // "real" angles
    int b1, b2, b3;    // BAMS angles


    // get some input
    printf("Start Angle ? ");
    scanf("%lf", &a1);

    printf("Goal Angle ? ");
    scanf("%lf", &a2);

    b1 = TOBAMS(a1);
    b2 = TOBAMS(a2);

    // difference increases with increasing goal angle
    // difference decreases with increasing start angle
    b3 = b2 - b1;
    b3 &= 0xff;

    printf("Start at %7.2lf deg and go to %7.2lf deg\n", a1, a2);
    printf("BAMS   are 0x%02X and 0x%02X\n", b1, b2);
    printf("BAMS diff is 0x%02X\n", b3);

    // check what would be the 'sign bit' of the difference
    // negative (msb set) means turn one way, positive the other
    if( b3 & 0x80 )
    {
        // difference is negative; negate to recover the
        // DISTANCE to move, since the negative-ness just
        // indicates direction.

        // cheap 2's complement on an N-bit value:
        // invert, increment, trim
        b3 ^= -1;       // XOR -1 inverts all the bits
        b3 += 1;        // "add 1 to x" :P
        b3 &= 0xFF;     // retain only N bits

        // difference is already positive, can just use it
        printf("Turn left %lf degrees\n", TODEGS(b3));
        printf("Turn left %d counts\n", b3);
    }
    else
    {
        printf("Turn right %lf degrees\n", TODEGS(b3));
        printf("Turn right %d counts\n", b3);
    }

    return 0;
}
20 голосов
/ 26 июня 2009

Если вам нужно повернуть более чем на 180 градусов в одном направлении, чтобы навести револьверную головку, то быстрее будет повернуть другое направление.

Я бы просто проверил это, а затем повернул в соответствующем направлении

if (objdeg != gundeg)
{
    if ((gundeg - objdeg) > 180)
       gundeg++;
    else
       gundeg--;
}

РЕДАКТИРОВАТЬ: новое решение

Я уточнил свое решение на основе отзывов в комментариях. Это определяет, будет ли цель «направо или налево» от башни и решает, в какую сторону повернуть. Затем он инвертирует это направление, если цель находится на расстоянии более 180 градусов.

if (objdeg != gundeg)
{
  int change = 0;
  int diff = (gundeg - objdeg)%360;
  if (diff < 0)
     change = 1;
  else
     change = -1;

  if (Math.Abs(diff) > 180)
     change = 0 - change;

  gundeg += change;
 }
12 голосов
/ 26 июня 2009

К нормализовано до [0,360):

(т. Е. Половина открытого диапазона)

Используйте оператор модуля для выполнения «получения остатка от деления»:

361 % 360

будет 1.

В языках стиля C / C ++ / ... это будет

gundeg %= 360

Примечание (благодаря комментарию): если gundeg является типом с плавающей запятой, вам нужно будет либо использовать библиотечную функцию в C / C ++: fmod, либо сделать это самостоятельно (.NET):

double FMod(double a, double b) {
  return a - Math.floor(a / b) * b;
}

Какой путь повернуть?

Какой путь короче (а если поворот на 180 °, то ответ произвольный), в C #, и предполагается, что направление измеряется против часовой стрелки

TurnDirection WhichWayToTurn(double currentDirection, double targetDirection) {
  Debug.Assert(currentDirection >= 0.0 && currentDirection < 360.0
               && targetDirection >= 0.0 && targetDirection < 360.0);

  var diff = targetDirection - currentDirection ;
  if (Math.Abs(diff) <= FloatEpsilon) {
    return TurnDirection.None;
  } else if (diff > 0.0) {
    return TurnDirection.AntiClockwise;
  } else {
    return TurnDirection.Clockwise;
  }
}

Примечание. Требуется проверка.

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

Также обратите внимание. чтобы разобраться в таких вещах, нет ничего лучше, чем карандаш и бумага (моя первоначальная версия была неверной, потому что я смешивал, используя (-180,180] и [0,360).

10 голосов
/ 27 июня 2009

Я склоняюсь к решению, которое

  • не имеет много вложенных , если операторов
  • не предполагает, что любой из двух углов находится в определенном диапазоне, например [0, 360] или [-180, 180]
  • имеет постоянное время выполнения

Решение для перекрестного произведения, предложенное Krypes , удовлетворяет этому критерию, однако сначала необходимо получить векторы из углов. Я считаю, что метод * BAMS JustJeff также удовлетворяет этому критерию. Я предложу еще ...

Как обсуждено на Почему модуль отличается в разных языках программирования? , который относится к превосходной статье Википедии , есть много способов выполнить операцию по модулю. Общие реализации округляют частное к нулю или отрицательной бесконечности.

Если, однако, вы округляете до ближайшего целого числа:

double ModNearestInt(double a, double b) {
    return a - b * round(a / b);
}

Имеет свойство nice, которое возвращает остаток

  • всегда в интервале [-b / 2, + b / 2]
  • всегда кратчайшее расстояние до нуля

Итак,

double angleToTarget = ModNearestInt(objdeg - gundeg, 360.0);

будет наименьшим углом между objdeg и gundeg, а знак будет указывать направление.

6 голосов
/ 26 июня 2009

Просто сравните следующее:

gundeg - objdeg
objdeg - gundeg 
gundeg - objdeg + 360
objdeg - gundeg + 360

и выберите тот с минимальным абсолютным значением .

5 голосов
/ 26 июня 2009

Вот пример с рабочим знаком C #, который повернет в правильном направлении. :

public class Rotater
{
    int _position;
    public Rotater()
    {

    }
    public int Position
    {
        get
        {
            return _position;
        }
        set            
        {
            if (value < 0)
            {
                _position = 360 + value;
            }
            else
            {
                _position = value;
            }
            _position %= 360;
        }
    }
    public bool RotateTowardsEx(int item)
    {
        if (item > Position)
        {
            if (item - Position < 180)
            {
                Position++;
            }
            else
            {
                Position--;
            }
            return false;
        }
        else if (Position > item)
        {
            if (Position - item < 180)
            {
                Position--;
            }
            else
            {
                Position++;
            }
            return false;
        }
        else
        {
            return true;
        }
    }
}

    static void Main(string[] args)
    {


        do
        {
            Rotater rot = new Rotater();
            Console.Write("Enter Starting Point: ");
            var startingPoint = int.Parse(Console.ReadLine());
            rot.Position = startingPoint;
            int turns = 0;

            Console.Write("Enter Item Point: ");
            var item = int.Parse(Console.ReadLine());
            while (!rot.RotateTowardsEx(item))
            {
                turns++;
            }
            Console.WriteLine(string.Format("{0} turns to go from {1} to {2}", turns, startingPoint, item));
        } while (Console.ReadLine() != "q");


    }

Благодарность Джону Пири за вдохновение

Редактировать: Я не был доволен своим установщиком положения, поэтому я очистил его

3 голосов
/ 26 июня 2009

На самом деле, есть более простой способ решения этой проблемы. Перекрестное произведение двух векторов дает вам вектор, представляющий нормаль (например, перпендикулярно). В качестве артефакта этого, учитывая два вектора a, b, которые лежат на плоскости xy, a x b = c подразумевает c = (0,0, + -1).

Знак z-компонента c (например, выходит ли он из плоскости xy или выходит из нее) зависит от того, повернут ли он влево или вправо вокруг оси z, чтобы a было равно b.

Vector3d turret
Vector3d enemy

if turret.equals(enemy) return;
Vector3d normal = turret.Cross(enemy);
gundeg += normal.z > 0 ? 1 : -1; // counter clockwise = +ve
3 голосов
/ 26 июня 2009

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

if (objdeg > gundeg)
{
    if (objdeg - gundeg < 180)
    {
        gundeg++;
    }
    else
    {
        gundeg--;
    }
}
if (objdeg < gundeg)
{
    if (gundeg - objdeg < 180)
    {
        gundeg--;
    }
    else
    {
        gundeg++;
    }
}
if (gundeg < 0)
{
    gundeg += 360;
}
gundeg = gundeg % 360;
1 голос
/ 26 июня 2009

Попробуйте разделить на 180, используя целочисленное деление и поворот на основе четного / нечетного результата?

749/180 = 4 Таким образом, вы поворачиваетесь по часовой стрелке на 29 градусов (749% 180)

719/180 = 3 Таким образом, вы поворачиваетесь против часовой стрелки на 1 градус (180 - 719% 180)

0 голосов
/ 08 октября 2016

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

var diff = angle_difference(gundeg, objdeg)

if (sign(diff)>0){
   gundeg --;
}else{
   gundeg ++;
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...