Mouvement circulaire sur du matériel de faible puissance

10

Je pensais aux plates-formes et aux ennemis qui tournaient en rond dans les vieux jeux 2D, et je me demandais comment cela se faisait. Je comprends les équations paramétriques, et il est trivial d'utiliser sin et cos pour le faire, mais un NES ou un SNES pourrait-il faire des appels trig en temps réel? J'admets une grande ignorance, mais je pensais que ces opérations étaient coûteuses. Existe-t-il un moyen intelligent de calculer ce mouvement à moindre coût?

J'ai travaillé sur la dérivation d'un algorithme à partir d'identités de somme de trigonomètres qui n'utiliseraient que des triggers précalculés, mais cela semble compliqué.

akroy
la source
1
On m'a en fait posé cette question lors d'un entretien d'embauche il y a plusieurs années.
Crashworks

Réponses:

14

Sur le matériel tel que vous le décrivez, une solution courante au cas général consiste simplement à produire une table de recherche pour les fonctions de trigonométrie qui vous intéressaient, parfois en conjonction avec des représentations à virgule fixe pour les valeurs.

Le problème potentiel de cette technique est qu'elle consomme de l'espace mémoire, bien que vous puissiez minimiser cela en optant pour une résolution inférieure des données dans votre table ou en profitant de la nature périodique de certaines fonctions pour stocker moins de données et les refléter lors de l'exécution.

Cependant, pour traverser spécifiquement les cercles - soit pour les pixelliser, soit pour déplacer quelque chose le long d'un, une variante de l'algorithme de ligne de Bresenham peut être utilisée . L'algorithme réel de Bresenham , bien sûr, est également utile pour traverser des lignes qui ne sont pas dans les huit directions "primaires" à peu de frais.


la source
2
Histoire vraie. LUT et un cercle est défini comme 256 degrés, ce qui donne un trig pas cher, la mise en miroir n'a été effectuée que si la mémoire était serrée et en dernier recours pour gagner quelques octets. La référence Bresenham est également parfaite pour différents mouvements.
Patrick Hughes
4
Même sur du matériel moderne, un appel trig est toujours une table de recherche. C'est juste une table de recherche dans le matériel, avec un certain raffinement via une extension Taylor. (En fait, l'implémentation par un grand fabricant de consoles d'une fonction SIMD sin () est simplement une série Taylor codée en dur.)
Crashworks
3
@Crashworks: il n'y a absolument aucun moyen que ce soit une série Taylor, ce serait vraiment stupide. C'est très probablement un polynôme minimax. En fait, toutes les implémentations modernes de sin () que j'ai jamais vues sont basées sur des polynômes minimax.
sam hocevar
@SamHocevar pourrait l'être. Je viens de voir le résumé de hache + bx ^ 3 + cx ^ 5 + ... et j'ai supposé "série Taylor".
Crashworks
9

Il y a une variation de l' algorithme de Bresenham par James Frith , qui devrait être encore plus rapide car il élimine complètement la multiplication. Il n'a pas besoin de table de recherche pour y parvenir, bien que l'on puisse stocker les résultats dans une table si le rayon reste constant. Étant donné que l'algorithme de Bresenham et de Frith utilisent une symétrie 8 fois, cette table de recherche serait relativement courte.

// 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 //
ProphetV
la source
Si vous obtenez des résultats étranges, c'est parce que vous invoquez un comportement non défini (ou du moins non spécifié) . C ++ ne spécifie pas quel appel est évalué en premier lors de l'évaluation de "a () + b ()", et appelle en outre la modification des intégrales. Pour éviter cela, ne modifiez pas une variable dans la même expression que vous l'avez lue comme dans xoff++ + xoffet --yoff + yoff. Votre liste de modifications corrigera cela, envisagez de le fixer en place plutôt que comme une pointe sur la note. (Voir la section 5, paragraphe 4 du standard C ++ pour des exemples et le standard qui l'appelle explicitement)
MaulingMonkey
@MaulingMonkey: Vous avez raison sur l'ordre d'évaluation problématique de balance += xoff++ + xoffet balance -= --yoff + yoff. J'ai laissé cela inchangé, car c'était la façon dont l'algorithme de Frith avait été écrit à l'origine, avec le correctif ajouté plus tard par lui-même (voir ici ). Fixé maintenant.
ProphetV
2

Vous pouvez également utiliser une version approximative des fonctions trigonométriques à l'aide de Taylor Expansions http://en.wikipedia.org/wiki/Taylor_series

Par exemple, vous pouvez avoir une approximation raisonnable du sinus en utilisant ses quatre premiers termes de la série Taylor

sinus

Communauté
la source
C'est généralement vrai, mais il y a tellement de mises en garde que j'irais jusqu'à dire que vous ne devriez pratiquement jamais écrire votre propre code sin () à moins que vous ne soyez très familier avec ce que vous faites. En particulier, il existe (légèrement) de meilleurs polynômes que celui répertorié, des approximations rationnelles encore meilleures, et vous devez comprendre où appliquer la formule et comment utiliser la périodicité du péché et du cos pour affiner votre argument à une plage où le s'applique. C'est l'un de ces cas où l'ancien aphorisme «un peu de connaissance est une chose dangereuse» sonne vrai.
Steven Stadnicki
Pouvez-vous donner quelques références afin que je puisse apprendre ces polynômes ou d'autres meilleures approximations? Je veux vraiment l'apprendre. Cette série était la partie la plus époustouflante de mon cours de calcul.
L'endroit classique pour commencer est le livre Numerical Recipes, qui donne pas mal d'informations sur le calcul des fonctions numériques de base et les mathématiques derrière leurs approximations. Un autre endroit que vous pourriez rechercher, pour une approche un peu dépassée mais qui mérite tout de même d'être connue, est de rechercher le soi-disant algorithme CORDIC .
Steven Stadnicki
@Vandell: si vous voulez créer des polynômes minimax, je serais heureux d'entendre vos réflexions sur LolRemez .
sam hocevar
La série de Taylor se rapproche du comportement d'une fonction autour d'un seul point, pas sur un intervalle. Le polynôme est idéal pour évaluer sin (0) ou sa septième dérivée autour de x = 0, mais l'erreur à x = pi / 2, après quoi vous pouvez simplement refléter et répéter, est assez grande. Vous pouvez faire environ cinquante fois mieux en évaluant la série Taylor autour de x = pi / 4, mais ce que vous voulez vraiment, c'est un polynôme qui minimise l'erreur maximale sur l'intervalle, au détriment de la précision près d'un seul point.
Marcks Thomas
2

Un algorithme génial pour voyager uniformément sur un cercle est l' algorithme de Goertzel . Il ne nécessite que 2 multiplications et 2 ajouts par étape, aucune table de recherche et un état très minimal (4 chiffres).

Définissez d'abord certaines constantes, éventuellement codées en dur, en fonction de la taille de pas requise (dans ce cas, 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'algorithme utilise 4 nombres comme état, initialisé comme ceci:

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

Et enfin la boucle 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);
}

Cela peut alors durer éternellement. Voici les 50 premiers points:

Algorithme de Goertzel

L'algorithme peut bien sûr fonctionner sur du matériel à virgule fixe. La victoire nette contre Bresenham est la vitesse constante sur le cercle.

sam hocevar
la source