Movimento circolare su hardware a bassa potenza


10

Stavo pensando a piattaforme e nemici che si muovevano in cerchio nei vecchi giochi 2D, e mi chiedevo come fosse fatto. Capisco le equazioni parametriche ed è banale usare sin e cos per farlo, ma un NES o SNES può effettuare chiamate trig in tempo reale? Ammetto una forte ignoranza, ma pensavo che fossero operazioni costose. C'è un modo intelligente per calcolare quel movimento in modo più economico?

Ho lavorato per derivare un algoritmo da identità di trig sum che avrebbe utilizzato solo trig precalcolato, ma che sembra contorto.


1
Mi è stata effettivamente posta questa domanda durante un colloquio di lavoro diversi anni fa.
Crashworks,

Risposte:


14

Su hardware come quello che stai descrivendo, una soluzione comune al caso generale è semplicemente quella di produrre una tabella di consultazione per le funzioni di trigonometria a cui era interessato, a volte in combinazione con rappresentazioni in virgola fissa per valori.

Il potenziale problema con questa tecnica è che consuma spazio di memoria, sebbene sia possibile minimizzare questo accontentandosi di una risoluzione inferiore dei dati nella tabella o sfruttando la natura periodica di alcune funzioni per archiviare meno dati e rispecchiarli in fase di esecuzione.

Tuttavia, per attraversare in modo specifico i cerchi - o per rasterizzarli o per spostare qualcosa lungo uno, è possibile utilizzare una variante dell'algoritmo di linea di Bresenham . L'attuale algoritmo di Bresenham , ovviamente, è utile anche per attraversare linee che non sono abbastanza economiche nelle otto direzioni "primarie".


2
Storia vera. LUT e un cerchio sono definiti come 256 gradi che producono trigoni economici, il mirroring è stato fatto solo se la memoria era stretta e come ultima risorsa per guadagnare qualche byte. Il riferimento di Bresenham è perfetto anche per movimenti diversi.
Patrick Hughes,

4
Anche su hardware moderno, un trig call è ancora una tabella di ricerca. È solo una tabella di ricerca nell'hardware, con qualche raffinamento attraverso un'espansione di Taylor. (Infatti l'implementazione di una delle principali funzioni sin () di SIMD sin () da parte di un produttore di console principale è semplicemente una serie di Taylor hardcoded.)
Crashworks,

3
@ Crashworks: non c'è assolutamente modo che sia una serie di Taylor, sarebbe davvero stupida da parte loro. Molto probabilmente è un polinomio minimax. In realtà, tutte le implementazioni moderne di sin () che abbia mai visto sono basate su polinomi minimix.
Sam Hocevar,

@SamHocevar Potrebbe essere. Ho appena visto il riassunto di ax + bx ^ 3 + cx ^ 5 + ... e ho assunto "serie Taylor".
Crashworks,

9

C'è una variante di algoritmo di Bresenham da James Frith , che dovrebbe essere ancora più veloce in quanto elimina completamente la moltiplicazione. Non è necessaria alcuna tabella di ricerca per raggiungere questo obiettivo, anche se è possibile memorizzare i risultati in una tabella se il raggio rimane costante. Poiché sia ​​l'algoritmo di Bresenham che quello di Frith usano una simmetria 8 volte, questa tabella di ricerca sarebbe relativamente breve.

// 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 //

Se stai ottenendo risultati strani, è perché stai invocando un comportamento indefinito (o almeno non specificato) . C ++ non specifica quale chiamata viene valutata per prima quando si valuta "a () + b ()", e chiama ulteriormente gli integrali modificanti. Per evitare ciò, non modificare una variabile nella stessa espressione in cui la leggi come in xoff++ + xoffe --yoff + yoff. Il tuo elenco modifiche risolverà questo problema, considera di fissarlo in posizione anziché come virata sulla nota. (Vedi la sezione 5, paragrafo 4 della norma C ++ per esempi e la norma che lo chiama esplicitamente)
MaulingMonkey,

@MaulingMonkey: hai ragione sull'ordine problematico di valutazione di balance += xoff++ + xoffe balance -= --yoff + yoff. L'ho lasciato invariato, poiché era così che era stato originariamente scritto l'algoritmo di Frith, con la correzione successivamente aggiunta da lui stesso (vedi qui ). Riparato ora.
ProphetV,

2

È inoltre possibile utilizzare una versione approssimata delle funzioni di trigitto utilizzando Taylor Expansions http://en.wikipedia.org/wiki/Taylor_series

Ad esempio, puoi avere una ragionevole approssimazione del seno usando i suoi primi quattro termini della serie taylor

seno


Questo è generalmente vero, ma viene fornito con così tanti avvertimenti che andrei fino al punto di dire che non dovresti praticamente mai scrivere il tuo codice sin () a meno che tu non abbia molta familiarità con quello che stai facendo. In particolare, ci sono polinomi (marginalmente) migliori di quello elencato, approssimazioni razionali ancora migliori, e devi capire dove applicare la formula e come usare la periodicità di sin e cos per restringere il tuo argomento a un intervallo in cui il si applica la serie. Questo è uno di quei casi in cui il vecchio aforisma "una piccola conoscenza è una cosa pericolosa" suona vero.
Steven Stadnicki,

Puoi darci dei riferimenti in modo che io possa imparare questi polinomi o altre approssimazioni migliori? Voglio davvero impararlo. Questa cosa della serie è stata la parte più strabiliante del mio corso di calcolo.

Il classico punto di partenza è il libro Ricette numeriche, che fornisce un bel po 'di informazioni sul calcolo delle funzioni numeriche fondamentali e della matematica dietro le loro approssimazioni. Un altro posto che potresti cercare, per un approccio un po 'obsoleto ma di cui vale la pena conoscere, è cercare il cosiddetto algoritmo CORDIC .
Steven Stadnicki,

@Vandell: se vuoi creare polinomi minimax, sarei felice di sentire le tue opinioni su LolRemez .
Sam Hocevar,

La serie di Taylor approssima il comportamento di una funzione attorno a un singolo punto, non su un intervallo. Il polinomio è ottimo per valutare sin (0) o la sua settima derivata attorno a x = 0, ma l'errore in x = pi / 2, dopo il quale puoi semplicemente specchiare e ripetere, è piuttosto grande. Puoi fare una cinquantina di volte meglio valutando invece la serie di Taylor attorno a x = pi / 4, ma quello che vuoi davvero è un polinomio che minimizzi l'errore massimo sull'intervallo, a costo di precisione vicino a un singolo punto.
Marcks Thomas,

2

Un algoritmo fantastico per viaggiare uniformemente su un cerchio è l' algoritmo Goertzel . Richiede solo 2 moltiplicazioni e 2 aggiunte per passaggio, nessuna tabella di ricerca e uno stato molto minimo (4 numeri).

Definire innanzitutto alcune costanti, possibilmente codificate, in base alla dimensione del passo richiesta (in questo 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;

L'algoritmo utilizza 4 numeri come stato, inizializzato in questo modo:

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

E infine il ciclo principale:

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

Può quindi andare per sempre. Ecco i primi 50 punti:

Algoritmo di Goertzel

Naturalmente l'algoritmo può funzionare su hardware a virgola fissa. La chiara vittoria contro Bresenham è la velocità costante sul cerchio.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.