Silverlight 5(Beta)で Teapot + Blinn-Phong ⑤
ティーポットをマウスで回転・拡大縮小するためのトラックボールを実装します。まず Visual Studio の[プロジェクト]→[クラスの追加]で TrackBall.cs を追加します。
トラックボールの実装
この実装は WPF のサンプルコードにあった TrackBall.cs を Silverlight 5 (Beta) 用に改造したものです。基本的な考え方はそのままですが、WPF 版では Viewport3D をベースにしているため、回転などのデータの持ち方がWPF専用だったので、プライベート変数として回転をクォータニオンで、スケールを単精度浮動小数点で保持し、パブリック変数 Transform は行列型としました。
//private _rotation and _scale
private Quaternion _rotation = Quaternion.Identity;
private float _scale = 1.0f;
//public Transform
public Matrix Transform
{
get {
Matrix transform = Matrix.CreateScale(_scale);
transform *= Matrix.CreateFromQuaternion(_rotation);
return transform; }
}
FrameworkElement からイベントをもらってくる方法は、ほとんど変わっていません。キャプチャの代わりに押下状態を保持したり、マウスホイール イベントを追加したくらいです。
トラックボールの基本的な考え方は、マウスの2D座標を球(ボール)上の3D座標に変換し、マウスの移動を追跡(トラック)してクォータニオン(ある軸を中心とした回転)として表現することです。前者の2Dから3Dへの変換は ProjectToTrackball メソッドで行われており、これは全く変更していません。後者のクォータニオン生成は WPF で使えたメソッドなどが使えなかったので、GetQuaternion メソッドで実装しなおしました。
ベクトル a からベクトル b への回転は、回転軸(a と b の外積)と回転角度(a と b の内積の逆余弦)で表現できます。その回転軸と回転角度から Quaternion.CreateFromAxisAngle 静的メソッドを使ってクォータニオンを生成します。ここでは最適化は全く考慮していません。クォータニオンについての詳細は拙著リアルタイム レンダリングなどを参照してください。
private Quaternion GetQuaternion(Vector3 v0, Vector3 v1)
{
// Rotate and Normalize
Vector3 a = Vector3.Normalize(Vector3.Transform(v0, _rotation));
Vector3 b = Vector3.Normalize(Vector3.Transform(v1, _rotation));
// Cross product
Vector3 cross = Vector3.Cross(a, b);
cross = Vector3.Normalize(cross);
// angle
float dot = Vector3.Dot(a, b);
float angle = (float)Math.Acos((double)dot);
return Quaternion.CreateFromAxisAngle(cross, angle);
}
拡大縮小を処理するZoomでは、マウスホイールの返す値に縮小因子をかけてスケール値に加算しました。
private void Zoom(int delta)
{
_scale += 0.001f * delta ;
}
トラックボールの利用
MainPage.xaml.cs の MainPage コンストラクターで TrackBall クラスをインスタンス化し、XAMLで宣言したmyElementをイベントソースとして設定します。
private Trackball trackball;
public MainPage()
{
InitializeComponent();
// Create Trackball
trackball = new Trackball();
trackball.EventSource = myElement;
}
そして、描画時にティーポットのワールド行列(World プロパティ)にトラックボールの行列(Transformプロパティ)を代入します。依存プロパティにすればこれはいらないと思いますが、ここでの議論とは別の話なので、シンプルに毎回代入します。
void DrawingSurface_Draw(object sender, DrawEventArgs e)
{
teapot.World = trackball.Transform;
teapot.Draw(e.GraphicsDevice);
e.InvalidateSurface();
}
次回はシェーダー パラメータ(レジスタ)を Silverlight コントロールで制御します。
Trackball.csを添付します。