人気ブログランキング | 話題のタグを見る

ぬるぽを見かけたら 全力でぶっ叩くのみ


by Denullpo Smasher Hammerson
カレンダー
S M T W T F S
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31

2D,3Dプログラミング向け我流数学 其之参

[過去記事]
2D,3Dプログラミング向け我流数学 其之壹
2D,3Dプログラミング向け我流数学 其之弐

今回は、実践的なところで。

[お題]
平面上の任意点を原点周りでひたすら回転し続けるプログラムを考えるべし

普通に考えるなら、回転角を変数として変化させつつ、その都度回転公式を通して
いけばよい。以下の例では、ループの度に回転後の値が x2 と y2 に格納される。

x=INPUT_X;
y=INPUT_Y;
r=0.0; // 回転角
while(1){
    // 回転角のcos,sinを求める
    c=cos(r);
    s=sin(r);

    // 回転後の位置を求める
    x2=c*x-s*y;
    y2=s*x+c*y;

    // 回転角をずらす
    r+=0.1;
    }


…とまあ、こんな感じになるのだが、この例は処理効率の点でボツ。




何故なら、cos()とかsin()とかはコストが高い。この例ならともかく、大量の
座標処理を要するプログラムでは、こーゆーコストの高い関数を頻繁に
呼んでたら大きな無駄になる。
(x87後継プロセッサならハードウェアでやってくれるぶんマシだけど)

で、改良。
回転角は、初めからcosとsinのペアで保持していればよいのだ。回転角の
定量増減程度なら、加法定理だけで高速に算出できる。どうしてもラジアン値が
必要なら、atanで逆算してやればよい。
これはこれで無駄といえば無駄だが、実際には頻度の問題。少なくとも自分の
経験上、ラジアン値が必要になるケースは回転計算と比べれば微々たるもん。

x=INPUT_X;
y=INPUT_Y;
c=1.0; // 回転角cos
s=0.0; // 回転角sin
dc=cos(0.1); // 回転角変化量cos
ds=sin(0.1); // 回転角変化量sin
while(1){
    // 回転後の位置を求める
    x2=c*x-s*y;
    y2=s*x+c*y;

    // 回転角をずらす
    sc=c;
    ss=s;
    c=sc*dc-ss*ds;
    s=ss*dc+sc*ds;
    }


これでループ内の処理効率が格段によくなった。
が、まだまだ。入力値や回転角を保持しなくていいという前提なら、回転計算自体も
相対的に計算できる。

x=INPUT_X;
y=INPUT_Y;
dc=cos(0.1); // 回転角変化量cos
ds=sin(0.1); // 回転角変化量sin
while(1){
    // 回転後の位置を求める
    x2=dc*x-ds*y;
    y2=ds*x+dc*y;

    // 入力値を現在値で書き換える(ループ後は相対的に作用する)
    x=x2;
    y=y2;
    }


御覧の通り、さらに手数が減ったですよ。
ただし、相対的な計算というのは密かに弱点がある。
一般的なプロセッサが普通に扱う小数は、2-n(0.5, 0.25, 0.125, ...)の
倍数しか表現できなかったり。
つまり、それ以外の値(0.1とか)は保持するだけでも近似値となり、そこで誤差が
発生する。(電卓ソフトなんかでは10進数をエミュレートすることで精度を維持している)
で、その誤差でズレた位置から次を計算しているですよ。
それを延々と繰り返したら、どうなることやら。

どのぐらいの精度が要るかというのはケースバイケースだけど、こーやって
無限ループで何日も回しっぱなしとかいった要件下では、ある程度の間隔で
位置をリフレッシュしてやる必要がでてくるかも。
実際、そこまで要求されるような状況は今まで皆無だったけど。
by denullpo | 2008-04-16 23:33 | こっち関係