Ruch kołowy na sprzęcie o niskiej mocy

10

Myślałem o platformach i wrogach poruszających się w kółko w starych grach 2D i zastanawiałem się, jak to zrobić. Rozumiem równania parametryczne i do tego jest trywialne użycie sin i cos, ale czy NES lub SNES mogą wykonywać wywołania trig w czasie rzeczywistym? Przyznaję, że to duża ignorancja, ale myślałem, że to kosztowne operacje. Czy jest jakiś sprytny sposób na tańsze obliczenie tego ruchu?

Pracowałem nad uzyskaniem algorytmu z tożsamości sumy wyzwalającej, która używałaby tylko wstępnie obliczonego wyzwalacza, ale wydaje się to skomplikowane.

akroy
źródło
1
Pytanie to zostało mi zadane podczas rozmowy kwalifikacyjnej kilka lat temu.
Crashworks

Odpowiedzi:

14

Na sprzęcie, który opisujesz, powszechnym rozwiązaniem w ogólnym przypadku jest po prostu stworzenie tabeli przeglądowej dla funkcji trygonometrii, która była zainteresowana, czasami w połączeniu z stałymi punktami reprezentacji wartości.

Potencjalnym problemem związanym z tą techniką jest to, że zajmuje ona miejsce w pamięci, choć można to bagatelizować, wybierając niższą rozdzielczość danych w tabeli lub korzystając z okresowego charakteru niektórych funkcji, aby przechowywać mniej danych i dublować je w czasie wykonywania.

Jednak w przypadku szczególnie przemierzających kręgów - w celu ich rasteryzacji lub przesunięcia czegoś wzdłuż jednego, można zastosować odmianę algorytmu liniowego Bresenhama . Rzeczywisty algorytm Bresenhama jest oczywiście przydatny również w przypadku tanich linii, które nie są w ośmiu „pierwotnych” kierunkach.


źródło
2
Prawdziwa historia. LUT i okrąg definiuje się jako 256 stopni wydajności taniego wyzwalacza, tworzenie kopii lustrzanych wykonano tylko wtedy, gdy pamięć była napięta i w ostateczności uzyskano kilka bajtów. Odniesienie do Bresenhama jest również przydatne w przypadku różnych ruchów.
Patrick Hughes,
4
Nawet na nowoczesnym sprzęcie wywołanie trig jest nadal tabelą odnośników. To tylko sprzętowa tabela przeglądowa z pewnymi udoskonaleniami dzięki rozszerzeniu Taylora. (W rzeczywistości implementacja funkcji SIMD sin () przez jednego z głównych producentów konsol jest po prostu zakodowaną serią Taylora.)
Crashworks
3
@Crashworks: nie ma absolutnie żadnej możliwości, aby była to seria Taylora, byłoby naprawdę głupie z ich strony. Prawdopodobnie jest to wielomian minimax. Właściwie wszystkie współczesne implementacje sin (), jakie kiedykolwiek widziałem, oparte są na wielomianach minimax.
sam hocevar
@SamHocevar Może być. Właśnie widziałem sumowanie siekiery + bx ^ 3 + cx ^ 5 + ... i założyłem „szereg Taylora”.
Crashworks
9

Istnieje odmiana algorytmu Bresenhama autorstwa Jamesa Fritha , która powinna być jeszcze szybsza, ponieważ całkowicie eliminuje mnożenie. Aby to osiągnąć, nie potrzebuje żadnej tabeli przeglądowej, chociaż można przechowywać wyniki w tabeli, jeśli promień pozostaje stały. Ponieważ algorytm Bresenhama i Fritha używa 8-krotnej symetrii, ta tabela przeglądowa byłaby stosunkowo krótka.

// 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 //
Prorok
źródło
Jeśli otrzymujesz dziwne wyniki, to dlatego , że wywołujesz niezdefiniowane (lub przynajmniej nieokreślone) zachowanie . C ++ nie określa, które wywołanie jest oceniane jako pierwsze podczas oceny „a () + b ()”, a ponadto wywołuje modyfikację całek. Aby tego uniknąć, nie modyfikuj zmiennej w tym samym wyrażeniu, które czytasz jak w xoff++ + xoffi --yoff + yoff. Twoja lista zmian to naprawi, zastanów się nad ustaleniem go na miejscu zamiast jako notatką. (Zobacz przykłady w sekcji 5 ust. 4 standardu C ++ oraz standardese, który wyraźnie to woła)
MaulingMonkey,
@MaulingMonkey: Masz rację co do problematycznej kolejności oceny balance += xoff++ + xoffi balance -= --yoff + yoff. Pozostawiłem to bez zmian, ponieważ tak pierwotnie napisano algorytm Fritha, a poprawkę później sam dodał (patrz tutaj ). Naprawiono teraz.
ProphetV
2

Możesz także użyć przybliżonej wersji funkcji wyzwalacza za pomocą Taylor Expansions http://en.wikipedia.org/wiki/Taylor_series

Na przykład, możesz uzyskać rozsądne przybliżenie sinusu, używając jego pierwszych czterech terminów z serii Taylor

sinus

Społeczność
źródło
Na ogół jest to prawda, ale pochodzi z tyloma zastrzeżeniami, że chciałbym iść tak daleko, aby powiedzieć, że nigdy nie powinno praktycznie napisać własny sin () kod, chyba że jesteś bardzo obeznany z tym, co robisz. W szczególności istnieją (marginalnie) lepsze wielomiany niż te wymienione, jeszcze lepsze racjonalne przybliżenia, i musisz zrozumieć, gdzie zastosować formułę i jak korzystać z okresowości grzechu i cos, aby zawęzić argument do zakresu, w którym seria obowiązuje. To jeden z tych przypadków, w których stary aforyzm „odrobina wiedzy jest niebezpieczną rzeczą” brzmi prawdziwie.
Steven Stadnicki
Czy możesz podać jakieś referencje, abym mógł nauczyć się tych wielomianów lub innych lepszych przybliżeń? Naprawdę chcę się tego nauczyć. Ta seria była najbardziej oszałamiającą częścią mojego rachunku różniczkowego.
Klasycznym miejscem na początek jest książka Przepisy numeryczne, która zawiera sporo informacji na temat obliczania podstawowych funkcji numerycznych i matematyki leżących u ich podstaw. Innym miejscem, w którym możesz spojrzeć na podejście nieco przestarzałe, ale wciąż warte poznania, jest poszukiwanie tak zwanego algorytmu CORDIC .
Steven Stadnicki
@ Vandell: jeśli chcesz stworzyć wielomian minimax, chętnie usłyszę twoje przemyślenia na temat LolRemez .
sam hocevar
Szereg Taylora aproksymuje zachowanie funkcji wokół jednego punktu, a nie w przedziale. Wielomian świetnie nadaje się do oceny sin (0) lub jego siódmej pochodnej wokół x = 0, ale błąd przy x = pi / 2, po którym można po prostu wykonać kopię lustrzaną i powtórzyć, jest dość duży. Zamiast tego możesz zrobić około pięćdziesiąt razy lepiej, oceniając szereg Taylora wokół x = pi / 4, ale tak naprawdę chcesz wielomianu, który minimalizuje maksymalny błąd przedziału, kosztem precyzji w pobliżu jednego punktu.
Marcks Thomas
2

Jednym z niesamowitych algorytmów do równomiernego podróżowania po okręgu jest algorytm Goertzela . Wymaga tylko 2 zwielokrotnień i 2 dodatków na krok, bez tablicy odnośników i bardzo minimalnego stanu (4 liczby).

Najpierw zdefiniuj niektóre stałe, być może zakodowane na stałe, w oparciu o wymagany rozmiar kroku (w tym przypadku 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;

Algorytm wykorzystuje 4 liczby jako swój stan, zainicjowany w następujący sposób:

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

I wreszcie główna pętla:

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

Może wtedy trwać wiecznie. Oto pierwsze 50 punktów:

Algorytm Goertzela

Algorytm może oczywiście działać na sprzęcie o stałym punkcie. Wyraźną wygraną z Bresenhamem jest stała prędkość nad kołem.

sam hocevar
źródło