リクエストがあったので、「OPENTKでマウスを使った立体の自由移動 平行投影編」を作成しました。

メールでこんな相談をうけました。趣旨は以下のとおりです。

戦車のプログラムを参考にいろいろと試行錯誤しておりますが、なかなか上手くできない。

やろうとしていることは

平行投影で直方体を描画する。頂点の座標は(0,0,0)、向かいの頂点の座標を(-200,-100,-20)とする。
原点にXYZ方向に矢印を表示する
XY平面のZ座標0の位置に格子を描画する
マウスを使って移動、回転、縮小拡大をする

はじめに立体(50,50,0)(-50,-50,-50)で作成して、それなりに良い感じにできたが、直方体の初期座標を(0,0,0)(-200,-100,-20)に変更したところ、回転や拡大縮小が常に原点(0,0,0)を基準に処理されてしまう・・・

まとめるとこんな感じです。戦車のプログラムとはC# OpenTKで戦車を描画するのことです。

実際に質問者様が作成されたコードも添付されていたのですが、たしかにC# OpenTKで戦車を描画するを参考にしたらしく、自作メソッド名やプロパティ名がどこかで見たようなものが散見されました。

では質問と期待に応えるべくコーディングしていきましょう。

最初にZairyoXYZという配列型のフィールド変数がありますが、これが直方体を描画するための座標です。DrawBoxメソッドを実行すれば直方体が描画されます。

中心座標を(0,0,0)にすれば回転や拡大の処理が簡単にできそうですが、質問者様が作ろうとしているものの性格上、こうであったほうがいいようです。しかも拡大縮小と回転の中心はマウスがある部分にしてほしいというのです。

まずはコンストラクタのなかでイベントハンドラを追加しましょう。マウスボタンが押されたとき、離されたとき、移動しているとき、マウスホイールが回転しているときに描画状態を変更するための処理をすることになります。

Projection の設定ですが、透視投影ではなく平行投影を採用したいとのことです。ゲームをつくるときは近くにあるものは大きく、遠くにあるものは小さく描画される透視投影のほうがいいのかもしれませんが、図面やデザインイラストには平行投影が適しています。平行投影だと並行に並んだ辺が、どれだけ遠くにあっても同じ長さに見えます。

まずSetProjectionメソッドを以下のように変更します。CreateOrthographicOffCenterの引数は(left, right, bottom, top)に設定した数値が一番左、右、下、上の座標になります。マウスの位置を回転や拡大処理の軸にしたいということなので、そのまま割り当ててしまいます。これでクライアント座標を取得すれば描画対象の座標と一致します。

残りの2つの引数ですが、zNear~zFarの領域だけ描画します。この幅が少ないと近すぎる部分、遠すぎる部分が描画されず切れてしまいます。

以下はglControlがロードされたときにおこなわれる処理です。直方体を3倍の大きさで描画しようとしています。そのため自作メソッド SetInitScaleを呼び出しています。

これはglControlがリサイズされたときの処理です。

次にマウスの処理を考えます。左ボタンを押した状態でドラッグすれば平行移動、右ボタンを押した状態でドラッグすれば回転移動です。水平に移動させればY軸を中心に回転し、垂直に移動させればX軸を中心に回転移動します。

まず回転処理とオブジェクトを表示させる座標がプロパティで定義されていました。

マウス操作をしたときに上記のプロパティを適切に変更すればうまくいきそうです(実際にやってみるとハマりどころ満載でした)。

まずはマウスボタンが押されたら必要なデータを保存しておきます。マウスをクリックした位置、回転処理をするのであれば回転の軸になる座標を保存しておかなければなりません。

マウスがドラッグされている間はオブジェクトを移動、回転、拡大縮小させます。

次にマウスボタンが離されたときの処理を示します。マウス操作をするたびに直方体は平行移動と拡大、回転を繰り返します。しかも拡大、回転の軸になる座標は固定された値ではありません。

それらの値は記憶しておかないといけません。たんにX、Y、RotateX、RotateYのプロパティのそのときの値だけに気を取られているとうまくいきません。これに気がつかず昨日は完全にハマってしまいました。

ここでは回転の処理がおこなわれたかどうかを調べて、回転処理がおこなわれた場合はリストに保存しています。

RotateScaleInfoクラスとRotateInfoクラスはこのようになっています。

次にマウスホイールを動かしたときの処理ですが、マウスがある位置を中心にして拡大縮小がおこなわれるようにしなければなりません。拡大縮小率や拡大縮小の中心の座標が変化した場合はその情報をリストに格納するのですが、拡大してさらに拡大した場合は前回の拡大率との比を格納しなければなりません。これも気がつかないとハマってしまいます。

次に描画の処理を考えます。視点を変更するというコメントがありますが、実際にマウス操作で視点が変更されることはないので今回は呼び出す必要はありません。

DrawObjectメソッドで直方体を描画します。拡大したり回転したりというこれまでの操作情報が格納されているリストからデータを読み出して処理をおこないます。ただしいままさにマウスをドラッグして回転処理がおこなわれている場合は最初にこの処理を実行します(一番最後におこなわれた処理が最初になるため)。

DrawLine1メソッドとDrawLine2メソッドがありますが、これは矢印のついたXYZ座標軸と格子上の直線、直方体上に円を描画するためのものです。

ミドルクリックをすると設定がクリアされ、最初に描画された状態に戻ります。そのための処理を示します。