Movimento circular em hardware de baixa potência

10

Eu estava pensando em plataformas e inimigos se movendo em círculos nos antigos jogos 2D, e fiquei imaginando como isso foi feito. Entendo equações paramétricas, e é trivial usar sin e cos para fazer isso, mas um NES ou SNES poderia fazer chamadas em tempo real? Eu admito muita ignorância, mas achei que eram operações caras. Existe alguma maneira inteligente de calcular esse movimento mais barato?

Eu tenho trabalhado em derivar um algoritmo de identidades de soma trigonométrica que usariam somente trig pré-calculado, mas isso parece complicado.

Akroy
fonte
1
Na verdade, me fizeram essa pergunta durante uma entrevista de emprego há vários anos.
precisa

Respostas:

14

Em hardware como o que você está descrevendo, uma solução comum para o caso geral é simplesmente produzir uma tabela de consulta para as funções trigonométricas em que estava interessado, às vezes em conjunto com representações de ponto fixo para valores.

O possível problema com essa técnica é que ela consome espaço na memória, embora você possa minimizá-la, definindo uma resolução mais baixa dos dados em sua tabela ou aproveitando a natureza periódica de algumas funções para armazenar menos dados e espelhá-los em tempo de execução.

No entanto, para atravessar círculos especificamente - seja para rasterizá-los ou para mover algo ao longo de um, uma variação do algoritmo de linha de Bresenham pode ser empregada . O algoritmo real de Bresenham , é claro, também é útil para atravessar linhas que não estão nas oito direções "primárias" com um preço bastante baixo.


fonte
2
História real. LUT e um círculo são definidos como 256 graus, produzem trigonometrias baratos, o espelhamento só é feito se a memória estiver fraca e, como último recurso, para obter alguns bytes. A referência de Bresenham também está presente em diferentes movimentos.
Patrick Hughes
4
Mesmo no hardware moderno, uma chamada trigonométrica ainda é uma tabela de pesquisa. É apenas uma tabela de pesquisa em hardware, com algum refinamento por meio de uma expansão de Taylor. (Na verdade, a implementação de uma função SIM (sinD) de um grande fabricante de consoles é simplesmente uma série Taylor codificada.)
Crashworks
3
@Crashworks: não há absolutamente nenhuma maneira de ser uma série de Taylor, seria realmente estúpido da parte deles. Provavelmente é um polinômio minimax. Na verdade, todas as implementações modernas de sin () que eu já vi são baseadas em polinômios minimax.
Sam Hocevar
@SamHocevar Poderia ser. Acabei de ver a soma de ax + bx ^ 3 + cx ^ 5 + ... e assumi a "série Taylor".
precisa
9

Há uma variação de algoritmo Bresenham por James Frith , que deve ser ainda mais rápido, pois elimina completamente a multiplicação. Não é necessária nenhuma tabela de pesquisa para conseguir isso, embora seja possível armazenar os resultados em uma tabela se o raio permanecer constante. Como o algoritmo de Bresenham e Frith usa simetria 8 vezes, essa tabela de pesquisa seria relativamente curta.

// FCircle.c - Draws a circle using Frith's algorithm.
// Copyright (c) 1996  James E. Frith - All Rights Reserved.
// Email:  [email protected]

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 //
Profeta
fonte
Se você está obtendo resultados estranhos, é porque está invocando um comportamento indefinido (ou pelo menos não especificado) . O C ++ não especifica qual chamada é avaliada primeiro ao avaliar "a () + b ()" e chama ainda a modificação de integrais. Para evitar isso, não modifique uma variável na mesma expressão que você leu como em xoff++ + xoffe --yoff + yoff. Sua lista de alterações corrigirá isso, considere consertá-la no lugar, e não como uma tacha na nota. (Ver a secção 5 parágrafo 4 do padrão C ++ para os exemplos e a standardese que chama isto explicitamente)
MaulingMonkey
@MaulingMonkey: Você está certo sobre a ordem de avaliação problemática de balance += xoff++ + xoffe balance -= --yoff + yoff. Deixei isso inalterado, pois era assim que o algoritmo de Frith era originalmente escrito, com a correção posteriormente adicionada por ele (veja aqui ). Corrigido agora.
ProphetV
2

Você também pode usar uma versão aproximada das funções trigonométricas usando o Taylor Expansions http://en.wikipedia.org/wiki/Taylor_series

Por exemplo, você pode ter uma aproximação razoável do seno usando seus primeiros quatro termos da série taylor

seno

Comunidade
fonte
Isso geralmente é verdade, mas vem com tantas ressalvas que eu iria tão longe a ponto de dizer que você deve quase nunca escrever seu próprio código sin () a menos que você está muito familiarizado com o que você está fazendo. Em particular, existem polinômios (marginalmente) melhores do que o listado, aproximações racionais ainda melhores e você precisa entender onde aplicar a fórmula e como usar a periodicidade do pecado e cos para restringir seu argumento a um intervalo em que série se aplica. Este é um daqueles casos em que o velho aforismo "um pouco de conhecimento é uma coisa perigosa" soa verdadeiro.
Steven Stadnicki
Você pode dar algumas referências para que eu possa aprender esses polinômios ou outras aproximações melhores? Eu realmente quero aprender isso. Essa coisa de série foi a parte mais emocionante do meu curso de cálculo.
O lugar clássico para começar é o livro Receitas Numéricas, que fornece informações suficientes sobre como calcular as principais funções numéricas e a matemática por trás de suas aproximações. Outro lugar que você pode procurar, para uma abordagem um pouco desatualizada, mas que ainda vale a pena conhecer, é procurar o chamado algoritmo CORDIC .
Steven Stadnicki
@Vandell: se você quiser criar polinômios minimax, ficaria feliz em ouvir seus pensamentos sobre LolRemez .
21712 Sam 0:20
A série Taylor aproxima o comportamento de uma função em torno de um único ponto, não em um intervalo. O polinômio é ótimo para avaliar sin (0) ou sua sétima derivada em torno de x = 0, mas o erro em x = pi / 2, após o qual você pode simplesmente espelhar e repetir, é bastante grande. Você pode fazer cerca de cinquenta vezes melhor avaliando a série Taylor em torno de x = pi / 4, mas o que você realmente deseja é um polinômio que minimize o erro máximo no intervalo, ao custo da precisão próximo a um único ponto.
Marcks Thomas
2

Um algoritmo impressionante para viajar uniformemente sobre um círculo é o algoritmo de Goertzel . Requer apenas 2 multiplicações e 2 adições por etapa, nenhuma tabela de pesquisa e um estado muito mínimo (4 números).

Primeiro, defina algumas constantes, possivelmente codificadas, com base no tamanho da etapa necessária (neste caso, 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;

O algoritmo usa 4 números como seu estado, inicializado assim:

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

E finalmente o loop principal:

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

Pode então ir para sempre. Aqui estão os primeiros 50 pontos:

Algoritmo de Goertzel

Obviamente, o algoritmo pode funcionar em hardware de ponto fixo. A vitória clara contra o Bresenham é a velocidade constante sobre o círculo.

sam hocevar
fonte