Недавно я обнаружил EQATEC Profiler http://www.eqatec.com/tools/profiler . Он работает с большинством версий .NET и на множестве платформ. Он прост в использовании, а его части бесплатны даже для коммерческого использования.
Если вам нужно повернуть турель более чем на 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;
}
Вы можете полностью избежать деления (и модуляции), если представите свои углы в чем-то, называемом «BAMS», что означает двоичная система измерения углов. Идея состоит в том, что если вы сохраняете свои углы в N-битном целом числе, вы используете весь диапазон этого целого числа для представления угла. Таким образом, не нужно беспокоиться о переполнении после 360, потому что естественные свойства по модулю 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 бит с помощью логической операции И, например, angle & = 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;
}
Просто сравните следующее:
gundeg - objdeg
objdeg - gundeg
gundeg - objdeg + 360
objdeg - gundeg + 360
и выберите тот с минимальным абсолютным значением .
Вот рабочий пример 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");
}
Благодарим Джона Пири за вдохновение.
Редактировать: Мне не понравилось мое средство определения позиции, поэтому я убрал его
Вам нужно решить, повернуть влево или вправо, в зависимости от того, какое расстояние меньше. Затем вам нужно будет взять модуль:
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;
Проблема заключается в том, чтобы найти направление, которое даст кратчайшее расстояние.
Однако вычитание может привести к отрицательным числам, и это необходимо учитывать.
Если вы перемещаете пистолет на один шаг при каждой проверке, я не знаю, когда вы сделаете модуль упругости.
И, если вы хотите переместить ружье за один шаг, вы должны правильно добавить / вычесть дельту.
С этой целью Киршштейн , кажется, думает ближе всех ко мне.
Я работаю с целым числом в этом простом псудокоде.
if (objdeg != gundeg)
{
// we still need to move the gun
delta = gundeg - objdeg
if (delta > 0)
if (unsigned(delta) > 180)
gundeg++;
else
gundeg--;
else // delta < 0
if (unsigned(delta) > 180)
gundeg--;
else
gundeg++;
if (gundeg == 360)
gundeg = 0;
else if (gundeg == -1)
gundeg = 359;
}
Попытайтесь работать с этим постепенно, используя gundeg = 10 и objdeg = 350, чтобы увидеть, как Gundeg будет перемещен с 10 на 0, а затем с 359 до 350.
Вот как я недавно реализовал нечто подобное в игре:
double gundeg;
// ...
double normalizeAngle(double angle)
{
while (angle >= 180.0)
{
angle -= 360.0;
}
while (angle < -180.0)
{
angle += 360.0;
}
return angle;
}
double aimAt(double objAngle)
{
double difference = normalizeAngle(objdeg - gundeg);
gundeg = normalizeAngle(gundeg + signum(difference));
}
Все угловые переменные ограничены до -180 .. + 180, что упрощает такие вычисления.
(т. Е. Полуоткрытый диапазон)
Используйте оператор модуля, чтобы выполнить «получение остатка от деления»:
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).
Попробуйте разделить на 180, используя целочисленное деление и повернув на четный / нечетный результат?
749/180 = 4 Итак, вы поворачиваете по часовой стрелке на 29 градусов (749% 180)
719 / 180 = 3 Таким образом, вы поворачиваете против часовой стрелки на 1 градус (180–719% 180)
Я предпочитаю решение, в котором
Решение перекрестного произведения, предложенное Krypes , соответствует этим критериям, однако необходимо сгенерировать векторы сначала с углов. Я считаю, что методика BAMS JustJeff также удовлетворяет этим критериям. Я предлагаю еще один ...
Как обсуждалось в Почему модуль различается на разных языках программирования? , который относится к превосходной статье Википедии , есть много способов выполнить операция по модулю. Обычные реализации округляют частное до нуля или отрицательной бесконечности.
Однако, если
На самом деле, есть более простой способ решить эту проблему. Перекрестное произведение двух векторов дает вам вектор, представляющий нормаль (например, перпендикуляр). В качестве артефакта этого, учитывая два вектора a, b, которые лежат на плоскости xy, axb = c подразумевает c = (0,0, + -1).
Знак компонента z вектора c (например. выходит ли он из плоскости xy или переходит в плоскость 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
Рискуя навесить велосипед, сохранение степеней в виде целого числа, а не в виде собственного класса может быть случаем «примитивной одержимости». Если я правильно помню, в книге «Программист-прагматик» предлагалось создать класс для хранения степеней и выполнения над ними операций.