低電力ハードウェアでの円運動


10

古い2Dゲームでプラットフォームと敵が円を描くように動くことを考えていました。パラメトリック方程式を理解しています。sinとcosを使用してそれを行うのは簡単ですが、NESまたはSNESがリアルタイムのトリガー呼び出しを行うことはできますか?私はかなりの無知を認めていますが、それらは高価な操作だと思いました。そのモーションをより安価に計算するための巧妙な方法はありますか?

私は、事前計算されたトリガーのみを使用するアルゴリズムをトリガー合計IDから導出するよう取り組んでいますが、複雑なようです。


1
私は数年前の就職の面接で実際にこの質問を受けました。
Crashworks 2012年

回答:


14

説明しているようなハードウェアでは、一般的なケースの一般的な解決策は、関心のある三角関数のルックアップテーブルを単に値の固定小数点表現と組み合わせて作成することです。

この手法の潜在的な問題は、メモリ領域を消費することですが、テーブル内のデータの解像度を低くするか、一部の関数の定期的な性質を利用して、格納するデータを減らし、実行時にミラーリングすることで、これを軽視することができます。

ただし、円を具体的に移動する場合-円をラスタライズするか、円に沿って何かを移動する場合は、ブレゼンハムの線アルゴリズムのバリエーションを使用できます。もちろん、Bresenhamの実際のアルゴリズムは、8つの「プライマリ」方向ではないラインを非常に安価にトラバースする場合にも役立ちます。


2
実話。LUTと円は256度の安価なトリガーとして定義されます。ミラーリングは、メモリが不足していて、数バイトを獲得する最後の手段としてのみ行われました。ブレゼンハムのリファレンスも、さまざまな動きにスポットを当てています。
Patrick Hughes

4
最新のハードウェアでも、trig呼び出しは依然としてルックアップテーブルです。これはハードウェアのルックアップテーブルにすぎず、テイラー展開による改良が加えられています。(実際には、SIMD sin()関数の1つの主要なコンソールメーカーの実装は、単にハードコードされたテイラーシリーズです。)
Crashworks '18 / 06/18

3
@Crashworks:それがテイラーシリーズである方法は絶対にありません、それは本当にそれらの愚かでしょう。それはおそらくミニマックス多項式です。実際、私が今まで見たsin()の最新の実装はすべて、ミニマックス多項式に基づいています。
sam hocevar

@SamHocevarかもしれません。ax + bx ^ 3 + cx ^ 5 + ...の合計を見て、「テイラー級数」と仮定しました。
Crashworks 2012年

9

James FrithによるBresenhamのアルゴリズムにはバリエーションがあり、乗算を完全に排除するため、さらに高速になります。これを実現するためにルックアップテーブルは必要ありませんが、半径が一定のままであれば、結果をテーブルに保存できます。ブレゼンハムとフリスのアルゴリズムはどちらも8重対称を使用するため、このルックアップテーブルは比較的短くなります。

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

奇妙な結果が得られる場合は、未定義の(または少なくとも未指定の)動作を呼び出しているためです。C ++は、 "a()+ b()"を評価するときに最初に評価される呼び出しを指定せず、さらに修正積分を呼び出します。これを避けるために、あなたは同様にそれを読むと同じ表現で変数を変更しないxoff++ + xoff--yoff + yoff。チェンジリストでこれを修正します。メモのタックとしてではなく、適切に修正することを検討してください。(例については、C ++標準のセクション5パラグラフ4と、これを明示的に呼び出す標準を参照してください)
MaulingMonkey

@MaulingMonkey:の問題の評価順序についてあなたがしている権利balance += xoff++ + xoffbalance -= --yoff + yoff。これは、Frithのアルゴリズムが最初に作成された方法であり、後で修正が自分で追加されたため、これを変更しませんでした(ここを参照)。今修正されました。
ProphetV 2012年

2

Taylor Expansions http://en.wikipedia.org/wiki/Taylor_seriesを使用して、trig関数の近似バージョンを使用することもできます

たとえば、テイラー級数の最初の4つの項を使用して、正弦の妥当な近似を得ることができます。

正弦


これは一般的には真実ですが、注意すべきことがたくさんあるので、自分がやっていること精通している場合を除いて、実際には独自のsin()コードを記述しないでください。特に、リストされているものよりも(わずかに)より良い多項式があり、さらにより有理近似があり、式を適用する場所と、sinおよびcosの周期性を使用して引数がシリーズが適用されます。これは、「少しの知識は危険なこと」という古い格言が真実であるケースの1つです。
Steven Stadnicki

この多項式または他のより良い近似を学習できるように、いくつかの参照を提供できますか?本当に学びたいです。このシリーズは、私の微積分コースの中で最も心を吹く部分でした。

開始する古典的な場所は本Numerical Recipesです。これは、コアとなる数値関数とその近似の背後にある数学の計算に関するかなりの情報を提供します。あなたが見るかもしれないもう1つの場所は、少し時代遅れですが、それでも知っておく価値のあるアプローチとして、いわゆるCORDICアルゴリズムを調べることです。
Steven Stadnicki

@ヴァンデル:ミニマックス多項式を作成したい場合は、LolRemezについてのご意見をお待ちしております。
sam hocevar 2012年

テイラー級数は、区間ではなく、単一点を中心とした関数の動作を近似します。多項式は、sin(0)またはx = 0の7次導関数を評価するのに最適ですが、x = pi / 2での誤差は非常に大きくなります。代わりにx = pi / 4の周りのテイラー級数を評価することで約50倍の効率を上げることができますが、本当に必要なのは、単一点付近の精度を犠牲にして、区間の最大誤差を最小化する多項式です。
Marcks Thomas

2

円周上を均一に移動する優れたアルゴリズムの 1つに、ゲルツェルアルゴリズムがあります。ステップごとに2つの乗算と2つの加算のみが必要で、ルックアップテーブルはなく、非常に最小限の状態(4つの数値)です。

最初に、必要なステップサイズ(この場合は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;

アルゴリズムは、次のように初期化された4つの数値を状態として使用します。

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

そして最後にメインループ:

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

その後、永遠に行くことができます。最初の50ポイントは次のとおりです。

Goertzelアルゴリズム

もちろん、アルゴリズムは固定小数点ハードウェアでも機能します。ブレゼンハムに対する明らかな勝利は、円周上の一定の速度です。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.