!!!三角関数 レンダラでは三角関数はよく使われます。 cos/sinなどの意味合いを元に説明します。 !!sin(サイン)とcos(コサイン) XY軸の空間で(0, 0)を中心として半径1.0の円を描きます。 このときの円周を通る点を考えます。X軸プラス方向(1, 0)を開始位置と したときのできる角度をθとしたときの円周上の点の位置を、 「(x, y) = (cosθ, sinθ)」とあらわします。 ちなみに、以下のように角度により位置情報をあらわす座標系を「'''極座標'''」と言います(一般のXY軸(またはXYZ軸)を持つ空間座標を「'''デカルト座標'''」と言います)。 {{ref_image cossin_20040831.png}} ぐるっと円周を回るわけですので、0度〜90度〜180度〜270度〜360度(= 0度)での cos/sinは以下の関係になります。 ,角度(θ),X = cosθ,Y = sinθ ,0,1,0 ,90,0,1 ,180,-1,0 ,270,0,-1 ,360,1,0 cosθは水平方向の-1.0〜+1.0の範囲を取り、sinθは垂直方向の-1.0〜+1.0の範囲を取ることが分かります。 !!θって何? では、次はcosθ/sinθにおける「θ」の意味合いです。上記では「度(°)」で 指定しましたが、C言語/数学では「'''ラジアン'''」という単位で指定します。 「360度 = 2π」というのが基礎となる単位と考えてください。 「π」は円周率を表し「3.1415926535...」と延々に続く数値です。 「360度 = 2π」なんで「180度 = π」、「90度 = 0.5π」ですね。 では「N度」では? #define PI 3.1415926535 #define SITA(c) ((PI * (c)) / 180.0) double fValS, fValC; double n = 45.0; fValC = cos(SITA(n)); fValS = sin(SITA(n)); で計算してしまうのが手っ取り早いです。 ラジアンから度数を求めるには上記の逆で、 n = (rad * 180.0) / PI; で度数を出すことができますね。 !!sinとcosの逆数 まずは極座標系で考えてください。 cosθ = X sinθ = Y と(X, Y)が求まっているときにθを求めるには、 アークコサイン(acos)・アークサイン(asin)というのを使います。 これは、それぞれcosとsinの逆数を取っているとも言えます。 まず、使用する前に(X, Y)が作るベクトルの長さが1.0になるように正規化します。 半径1.0の円の中心が(0 ,0)としたときの円周上の点の位置に持ってきます。 a = sqrt(X * X + Y * Y); X /= a; Y /= a; この段階でX, Yともに-1.0〜+1.0の値になっているはずです。 ここでacos/asinの出番です。 double a_c, a_s; a_c = acos(X); a_s = asin(Y); これで「a_c」「a_s」というのが求まりますが、片一方の結果だけでは 角度θは求まりません。 そこで、以下のような処理を加えてやります。 if(a_s > 0.0) rad = a_c; else rad = PI + PI - a_c; sinは垂直方向(Y)で-1.0〜+1.0を取る、cosは水平方向(X)で-1.0〜+1.0を取る、 といったことを元に、極座標での角度(ラジアン)0〜2πの間の値になるように 変換しています。 これで「rad」にはラジアンの形で角度が求まります。 最後に、これを度数に変換します。 n = (rad * 180.0) / PI; ここで求まったnが0.0〜360.0の角度を持つことになります。 !!2ベクトルの作る角度を求める それでは上記を踏まえた上で、2つの正規化された(長さが1.0になっている)ベクトルが作る角度を求めてみましょう。 {{ref_image kakudo_20040831.png}} (px1, py1)と(px2, py2)の2ベクトルがあるとします。ともに正規化済みとします。 ここで角度(cos)を求めるには2ベクトルの内積を求めるといいですので cVal = px1 * py1 + px2 * py2; で、cValに-1.0〜+1.0の数値が求まります。 プラスの値の場合は、2ベクトルの作る角度が0度〜90度の値ということになります。 ここで、実際の度数まで求めていきましょう。 rad = acos(cVal); これで角度がラジアンとして求まります。 逆に考えると、 cVal = cos(rad); です(acosはcosの逆数を取っているの意味)。最後にラジアンを度数に変換します。 n = (rad * 180.0) / PI; これで、nには2ベクトルの作る角度(0.0〜360.0)が求まります。 3次元空間では、cValでの内積を求める計算でZ値を考慮するだけです。 !!cos/sinの負荷 オフラインレンダラなどでは、普通にANSI Cの「math.h」でのcos/sin関数を 使っても速度が気になるということは少ないのですが、リアルタイムでは そのままでは重いです。 それくらい、ミリ秒単位の世界では三角関数の負荷が大きいとも言えます。 ですので、360度(一周)を256分割くらいして「'''ルックアップテーブル'''」で結果を 引き出す、ということもよく使われます。 double angle; for(i = 0; i < 256; i++) { angle = (double)i * PI / 128.0; m_cos[i] = (float)cos(angle); m_sin[i] = (float)sin(angle); } このとき、N度のcos/sinを求めるには以下のように計算できます(以下は90度でのcosとsinを求める場合)。 int n = 90; int index = (n << 8) / 360; valC = m_cos[index]; valS = m_sin[index]; 実際は除算がネックになるため、「360度で1回転」という考えを「256度で1回転」という概念に置き換えて考えます。 「N / 256」回転する場合(Nは0以上の整数)のcos/sinは N &= 0xff; valC = m_cos[N]; valS = m_sin[N];