球面座標を用いたカメラの移動・回転・拡大縮小　[初記事!]

なんか自分でも残せそうなものをちらほらと書いていきます。
といっても備忘録程度です。
一応あんまりWeb検索しても見つからないこと書いていきます。

なんてたってブログ形式なので、この情報が正しいかどうかは自分で考えてください。
一応自分ではこれが正しいのだと確信を持って書いております。

ですが自分はあまり頭がよろしくないので間違えて書いている場合があります。
その際は、遠慮なくコメント書いていってくれると助かります。

では本題に

#カメラワークについて

今回は球面座標を用いてカメラの回転、平行移動、拡大縮小を行います。
glでカメラ行列を指定する際は、多くの場合LookAtを用います。
球面座標、LookAtの図解は他サイトを見てくれると助かります。

>LookAt(Vector3 cameraPos,Vector3 lookPos,Vector3 cameraUp)
まぁ、いわずとしれたLookup関数ですね。
第1引数でカメラの位置を、
第2引数でカメラの見ている場所を
第3引数でカメラ自身がどの方向なのか
(カメラを普通に持つ場合(0,1,0)逆さに持つ場合(0,-1,0)、右向きに持ったら(1,0,0)等)
球面座標の表面上の1点がcameraPos球の中心がLookPosです。

##カメラの回転
図解を免れたところで球面座標を用いた回転を

カメラの回転とはというところをとりあえず書いておこう
先ほどの説明でいくと、「カメラは球の中心を見た状態で、球の表面上を移動させる」こと。
回転といいつつ移動もしているがまぁ、ある物体を真ん中においていろんな方向から
見るといった感じです。

では早速式を
rotate.X = Math.Cos(theta) * Math.Cos(phi);
rotate.Y = Math.Sin(phi);
rotate.Z = Math.Sin(theta) * Math.Cos(phi);

ここで使ってる関数はOpenTKのMath関数です。
thetaとphiはラジアン形式です角度じゃないです。
thetaはマウスの横方向の移動量
phiはマウスの縦方向の移動量
を入れといてください。
式の説明はできません！カメラの回転はこれを使ってくださいというだけです。
自分の説明は次からです。

##カメラの移動

カメラの移動とは、カメラも移動するし、見る場所も移動すること。
カメラがX軸に+1動いたら、注視点もXに+1動く。
(だから物体は左に動いたように見える)

そして式はこれです。
m_CameraPos = m_Pan + rotate;
m_LookAt = m_Pan;
m_Up = new Vector3(0,1,0);
LookAt(m_CameraPos,m_LookAt,m_Up);

ここでm_Panは移動量です。(3次元ベクトル)
rotateは回転で使ってたやつです。
まぁ上の説明で書いたままですね。
m_Panに(1,0,0)が入っていたらカメラはX軸に+1しますね。

##カメラの拡大縮小

カメラの拡大縮小？とはまぁ、カメラから物体までの距離です。
(よく自分が使ってるのでとりあえずこの言い方で)
近くから見てたら大きく、遠くから見てたら小さく見えるようにするために使います。
式です。
m_CameraPos = m_Pan + (rotate * m_Length);
m_LookAt = m_Pan;
m_Up = new Vector3(0,1,0);
LookAt(m_CameraPos,m_LookAt,m_Up);

ここでm_Lengthはカメラから、注視点までの距離です。(float)
この値が小さいと、物体に近づいていきます。
大きいとカメラがズームアウトするのでどんどん物体は離れていきます。

##カメラの移動方法

回転は、球面座標の使い方を使えばうまいこと回る
拡大縮小は値かえるだけだからマウスホイールでもトリガーにしてたら大丈夫となるだろう
だが移動方向はどうきめたらええんや？とお悩みの方がいるかもしれないのがこの記事を書こうと
思ったきっかけである。

ということで書いてくよいろいろあるだろうけど妥当そうなの1つしか書かないよ。


![カメラ.png](https://qiita-image-store.s3.amazonaws.com/0/63344/b0df85cf-4cee-5cbf-e52d-70e51305df60.png)

まずはこれを見ていただこう。
注目してほしいのは短いほうの赤い線と緑の線だ
これはカメラを移動、回転、拡大縮小させてもずっとこの方向を向いた線になる。
すなわちカメラの横方向(赤）と縦方向(緑)を示す線なのである。

>ちなみに赤と緑の線に直交する青色の線も表示しているが、カメラの向きとまったく一緒なので
見えない状態になっている。

この方向をどう取ってきてるのか分かれば、
回転させた後でもいい感じにカメラを移動させることができる。

この方向は、カメラの回転行列から取得することができる。
すなわちLookAtの戻り値（OpenTkではMatrix4)4*4行列の値です。
この行列の0行目が赤線、1行目が緑線、2行目が青線(画像では見えない)です。
ちなみに各行4つの値があるけど、4つ目は平行移動量なため不要です。

そして、これを用いて算出した平行移動量の式がこれ
vectorX = new Vector3(m_CameraMatrix.Column0);
vectorY = new Vector3(m_CameraMatrix.Column1);
vectorX *= move.X;
vectorY *= move.Y;
m_Pan = new Vector3(vectorX + vectorY);
            
ここでm_CameraMatrixはLookAt関数から取得できた4*4行列
Column0で0行目の値を取得してます。
ちなみにm_ModelMatrix.Column0は正規化されてます。
moveはマウスの移動量です。

これをカメラの平行移動で用いていたm_Panに入れて、カメラ位置を計算すれば
いい感じに平行移動できます。

##注意点と補足

###注意点
今回説明したカメラの移動回転平行移動結構簡単に実装できるので便利です。
便利がゆえに欠点もあります。

+ 1　上方向、下方向への回転は90度までの制約がある。
+ 2　マウス操作だけでは奥行き方向の移動が不可能？

まずは1番目から
今まであまり触れていなかったカメラの向きm_Upに該当する部分
このカメラの向きのせいで、カメラの上下方向の回転には制約がかかります。
動きとしては形状を手前、上面、背面とみようとカメラを回した場合、上面の途中で
形状がいきなりくるっと回転するのでよろしくないです。
そのため、phiに入れる値は[　-90度 < phi < 90度　]にしましょう

そして2つ目、今回の拡大縮小はあくまで注視点基準、なのでもっとカメラの位置を
奥にもっていきたいとしたときの操作が面倒になりますそして直感的ではありません。
（一旦カメラを横に向けて横方向に移動すれば奥にいける）
なので他のトリガーを用意するかあきらめるしかないです。

###補足
　注意点1を排除する方法として2通りあると考えています。
しかし、自分で実験してないのでこれらの方法にもなんらかの制約あるかも？

　１：カメラの回転を行うには、クォータニオンという4次元数を使う方法もあります。
しかし複雑です。

　２：今回はカメラを回転させる方法で行きましたが、カメラを回転させずに
写している形状全体を回転させて見え方を変える方法もあります。

などといったカメラの回転方法があるのでいろいろ試してみてください。