Круговое движение на маломощном оборудовании

10

Я думал о платформах и врагах, движущихся по кругу в старых 2D играх, и мне было интересно, как это было сделано. Я понимаю параметрические уравнения, и для этого достаточно просто использовать sin и cos, но могут ли NES или SNES совершать триггерные вызовы в реальном времени? Я допускаю тяжелое невежество, но я думал, что это были дорогостоящие операции. Есть ли какой-нибудь умный способ рассчитать это движение дешевле?

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

akroy
источник
1
На самом деле мне задавали этот вопрос во время собеседования несколько лет назад.
Crashworks

Ответы:

14

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

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

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


источник
2
Правдивая история. LUT и окружность, определенные как 256 градусов, дают дешевый триггер, зеркалирование выполнялось только при нехватке памяти и в качестве последнего средства для получения нескольких байтов. Ссылка на Брезенхэм также подходит для различных движений.
Патрик Хьюз
4
Даже на современном оборудовании триггерный вызов все еще является таблицей поиска. Это просто аппаратная таблица с некоторыми уточнениями благодаря расширению Тейлора. (На самом деле реализация одного из основных производителей консолей функции SIMD sin () - это просто жестко закодированная серия Тейлора.)
Crashworks
3
@Crashworks: нет абсолютно никакого способа, чтобы это был ряд Тейлора, это было бы действительно глупо с их стороны. Скорее всего, это минимаксный полином. На самом деле, все современные реализации sin (), которые я когда-либо видел, основаны на минимаксных полиномах.
Сам Хочевар
@SamHocevar Может быть. Я только что увидел суммирование топора + bx ^ 3 + cx ^ 5 + ... и предположил, что «ряд Тейлора».
Crashworks
9

Есть вариант алгоритма Брезенхэма от Джеймса Фрита , который должен быть еще быстрее, поскольку он полностью исключает умножение. Для этого не требуется никакой справочной таблицы, хотя можно сохранить результаты в таблице, если радиус остается постоянным. Поскольку оба алгоритма Брезенхэма и Фрита используют 8-кратную симметрию, эта таблица поиска будет относительно короткой.

// FCircle.c - Draws a circle using Frith's algorithm.
// Copyright (c) 1996  James E. Frith - All Rights Reserved.
// Email:  jfrith@compumedia.com

typedef unsigned char   uchar;
typedef unsigned int    uint;

extern void SetPixel(uint x, uint y, uchar color);

// FCircle --------------------------------------------
// Draws a circle using Frith's Algorithm.

void FCircle(int x, int y, int radius, uchar color)
{
  int balance, xoff, yoff;

  xoff = 0;
  yoff = radius;
  balance = -radius;

  do {
    SetPixel(x+xoff, y+yoff, color);
    SetPixel(x-xoff, y+yoff, color);
    SetPixel(x-xoff, y-yoff, color);
    SetPixel(x+xoff, y-yoff, color);
    SetPixel(x+yoff, y+xoff, color);
    SetPixel(x-yoff, y+xoff, color);
    SetPixel(x-yoff, y-xoff, color);
    SetPixel(x+yoff, y-xoff, color);

    balance += xoff++;
    if ((balance += xoff) >= 0)
        balance -= --yoff * 2;

  } while (xoff <= yoff);
} // FCircle //
ProphetV
источник
Если вы получаете странные результаты, это потому, что вы вызываете неопределенное (или, по крайней мере, неопределенное) поведение . C ++ не определяет, какой вызов оценивается первым при вычислении «a () + b ()», и далее вызывает модифицирующие интегралы. Чтобы избежать этого, не изменяйте переменную в том же выражении, которое вы прочитали, как в xoff++ + xoffи --yoff + yoff. Ваш список изменений исправит это, подумайте о том, чтобы исправить это на месте, а не как примечание. (См. Параграф 4 параграфа 5 стандарта C ++ для примеров и стандартов, в которых это явно выражено)
MaulingMonkey
@MaulingMonkey: Вы правы насчет проблемной оценки порядка balance += xoff++ + xoffи balance -= --yoff + yoff. Я оставил это без изменений, так как именно так был изначально написан алгоритм Фрита, а позднее исправление было добавлено им самим (см. Здесь ). Исправлено сейчас.
ProphetV
2

Вы также можете использовать приблизительную версию функций триггера, используя расширения Тейлора http://en.wikipedia.org/wiki/Taylor_series

Например, вы можете получить разумное приближение синуса, используя первые четыре члена ряда Тейлора

синус

Сообщество
источник
Как правило, это так, но в нем так много предостережений, что я бы сказал, что вы фактически никогда не должны писать свой собственный код sin (), если вы не очень хорошо знаете, что делаете. В частности, есть (незначительно) лучшие полиномы, чем приведенный выше, даже лучше рациональные приближения, и вам нужно понять, где применять формулу и как использовать периодичность sin и cos, чтобы сузить ваш аргумент до диапазона, где серия применяется. Это один из тех случаев, когда старый афоризм «немного знаний - опасная вещь» звучит правдоподобно.
Стивен Стадницкий,
Можете ли вы дать некоторые ссылки, чтобы я мог выучить эти полиномы или другие лучшие приближения? Я действительно хочу научиться этому. Эта серия была самой умопомрачительной частью моего курса исчисления.
Классическим местом для начала является книга «Численные рецепты», в которой дается немало информации о вычислении основных числовых функций и математики, лежащей в основе их приближений. Другое место, которое вы можете найти, для подхода, который немного устарел, но о котором все еще стоит знать, - это поиск так называемого алгоритма CORDIC .
Стивен Стадницки
@Vandell: если вы хотите создать минимаксные полиномы, я был бы рад услышать ваши мысли о LolRemez .
Сэм Хочевар
Ряд Тейлора приближает поведение функции вокруг одной точки, а не на интервале. Полином отлично подходит для оценки sin (0) или его седьмой производной около x = 0, но ошибка при x = pi / 2, после которой вы можете просто отразить и повторить, довольно велика. Вы можете сделать это примерно в пятьдесят раз лучше, оценив ряд Тейлора вместо x = pi / 4, но вы действительно хотите получить многочлен, который минимизирует максимальную ошибку на интервале за счет точности около одной точки.
отмечает Томас
2

Один удивительный алгоритм для равномерного перемещения по кругу - это алгоритм Гертцеля . Требуется только 2 умножения и 2 сложения за шаг, нет таблицы поиска и очень минимальное состояние (4 числа).

Сначала определите некоторые константы, возможно, жестко закодированные, исходя из требуемого размера шага (в данном случае 2π / 64):

float const step = 2.f * M_PI / 64;
float const s = sin(step);
float const c = cos(step);
float const m = 2.f * c;

Алгоритм использует 4 числа в качестве своего состояния, инициализированного так:

float t[4] = { s, c, 2.f * s * c, 1.f - 2.f * s * s };

И, наконец, основной цикл:

for (int i = 0; ; i++)
{
    float x = m * t[2] - t[0];
    float y = m * t[3] - t[1];
    t[0] = t[2]; t[1] = t[3]; t[2] = x; t[3] = y;
    printf("%f %f\n", x, y);
}

Это может продолжаться вечно. Вот первые 50 баллов:

Алгоритм Гёртцела

Алгоритм, конечно, может работать на оборудовании с фиксированной точкой. Чистая победа над Брезенхэмом - это постоянная скорость по кругу.

Сэм Хоцевар
источник