Movimiento circular en hardware de baja potencia.

10

Estaba pensando en plataformas y enemigos que se movían en círculos en viejos juegos 2D, y me preguntaba cómo se hacía eso. Entiendo ecuaciones paramétricas, y es trivial usar sin y cos para hacerlo, pero ¿podría un NES o SNES hacer llamadas trigonométricas en tiempo real? Admito una gran ignorancia, pero pensé que eran operaciones costosas. ¿Hay alguna forma inteligente de calcular ese movimiento de manera más barata?

He estado trabajando para derivar un algoritmo a partir de identidades de suma trigonométrica que solo usarían trigonométricas precalculadas, pero eso parece complicado.

akroy
fuente
1
De hecho, me hicieron esta pregunta durante una entrevista de trabajo hace varios años.
Crashworks

Respuestas:

14

En hardware como el que está describiendo, una solución común para el caso general es simplemente producir una tabla de búsqueda para las funciones de trigonometría que le interesaban, a veces junto con representaciones de valores de punto fijo.

El problema potencial con esta técnica es que consume espacio en la memoria, aunque puede minimizar esto al conformarse con una resolución más baja de los datos en su tabla o al aprovechar la naturaleza periódica de algunas funciones para almacenar menos datos y reflejarla en tiempo de ejecución.

Sin embargo, para atravesar círculos específicamente, ya sea para rasterizarlos o para mover algo a lo largo de uno, se puede emplear una variación del algoritmo de línea de Bresenham . El algoritmo real de Bresenham , por supuesto, también es útil para atravesar líneas que no están en las ocho direcciones "primarias" de manera bastante económica.


fuente
2
Historia verdadera. LUT y un círculo se define como 256 grados de rendimiento trigonométrico barato, la duplicación solo se realizó si la memoria era escasa y como último recurso para ganar unos pocos bytes. La referencia de Bresenham también es perfecta para diferentes movimientos.
Patrick Hughes
44
Incluso en hardware moderno, una llamada trigonométrica sigue siendo una tabla de búsqueda. Es solo una tabla de búsqueda en hardware, con cierto refinamiento a través de una expansión Taylor. (De hecho, la implementación de una función SIMD sin () de un fabricante importante de consolas es simplemente una serie de Taylor codificada)
Crashworks
3
@Crashworks: no hay forma de que sea una serie de Taylor, sería realmente estúpido de su parte. Es muy probable que sea un polinomio minimax. En realidad, todas las implementaciones modernas de sin () que he visto se basan en polinomios minimax.
sam hocevar
@SamHocevar podría ser. Acabo de ver el resumen de ax + bx ^ 3 + cx ^ 5 + ... y asumí la "serie de Taylor".
Crashworks
9

Hay una variación del algoritmo de Bresenham por James Frith , que debería ser aún más rápido ya que elimina por completo la multiplicación. No necesita ninguna tabla de búsqueda para lograr esto, aunque uno podría almacenar los resultados en una tabla si el radio se mantiene constante. Dado que tanto el algoritmo de Bresenham como el de Frith utilizan una simetría de 8 veces, esta tabla de búsqueda sería relativamente corta.

// 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 //
ProfetaV
fuente
Si obtiene resultados extraños, es porque está invocando un comportamiento indefinido (o al menos no especificado) . C ++ no especifica qué llamada se evalúa primero cuando se evalúa "a () + b ()", y luego llama a las integrales modificadoras. Para evitar esto, no modifique una variable en la misma expresión que la leyó en xoff++ + xoffy --yoff + yoff. Su lista de cambios arreglará esto, considere arreglarlo en su lugar en lugar de una tachuela en la nota. (Consulte la sección 5, párrafo 4, del estándar C ++ para ver ejemplos y el estándar que explícitamente lo llama)
MaulingMonkey
@MaulingMonkey: Tienes razón sobre el orden de evaluación problemático de balance += xoff++ + xoffy balance -= --yoff + yoff. Lo dejé sin cambios, ya que esta fue la forma en que se escribió originalmente el algoritmo de Frith, y luego agregó la solución (ver aquí ). Corregido ahora.
ProphetV
2

También puede usar una versión aproximada de las funciones trigonométricas usando Taylor Expansions http://en.wikipedia.org/wiki/Taylor_series

Por ejemplo, puede tener una aproximación razonable del seno utilizando sus primeros cuatro términos de la serie taylor

seno

Comunidad
fuente
Esto es generalmente cierto, pero viene con tantas advertencias que iría tan lejos como para decir que prácticamente nunca debería escribir su propio código sin () a menos que esté muy familiarizado con lo que está haciendo. En particular, hay polinomios (marginalmente) mejores que los enumerados, incluso mejores aproximaciones racionales, y debe comprender dónde aplicar la fórmula y cómo usar la periodicidad de sin y cos para reducir su argumento a un rango donde Se aplica la serie. Este es uno de esos casos en los que el viejo aforismo "un poco de conocimiento es algo peligroso" suena a verdad.
Steven Stadnicki
¿Puedes dar algunas referencias para que pueda aprender estos polinomios u otras aproximaciones mejores? Realmente quiero aprender eso. Esta serie fue la parte más alucinante de mi curso de cálculo.
El lugar clásico para comenzar es el libro Numerical Recipes, que proporciona una buena cantidad de información sobre el cálculo de las funciones numéricas centrales y las matemáticas detrás de sus aproximaciones. Otro lugar que puede buscar, para un enfoque que está un poco desactualizado pero que aún vale la pena conocer, es buscar el llamado algoritmo CORDIC .
Steven Stadnicki
@Vandell: si quieres crear polinomios minimax, me alegraría saber lo que piensas sobre LolRemez .
sam hocevar
La serie de Taylor aproxima el comportamiento de una función alrededor de un solo punto, no en un intervalo. El polinomio es excelente para evaluar sin (0) o su séptima derivada alrededor de x = 0, pero el error en x = pi / 2, después del cual puede simplemente reflejar y repetir, es bastante grande. Puede hacerlo unas cincuenta veces mejor evaluando la serie de Taylor alrededor de x = pi / 4, pero lo que realmente quiere es un polinomio que minimice el error máximo en el intervalo, a costa de la precisión cerca de un solo punto.
Marca Thomas el
2

Un algoritmo impresionante para viajar uniformemente sobre un círculo es el algoritmo de Goertzel . Requiere solo 2 multiplicaciones y 2 adiciones por paso, sin tabla de búsqueda y un estado muy mínimo (4 números).

Primero defina algunas constantes, posiblemente codificadas, basadas en el tamaño de paso requerido (en este 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;

El algoritmo usa 4 números como estado, inicializado así:

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

Y finalmente el bucle 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);
}

Entonces puede ir para siempre. Aquí están los primeros 50 puntos:

Algoritmo de Goertzel

El algoritmo, por supuesto, puede funcionar en hardware de punto fijo. La clara victoria contra Bresenham es la velocidad constante sobre el círculo.

sam hocevar
fuente