HitTest3D

Windows Presentation Foundationの特徴の一つには3Dグラフィックスがあります。以前紹介したように3Dの描画自身はそれほど難しくはありません。3Dを使ったメニューのようなものを作りたいとき必要なことは、マウスによるクリックがどの3Dオブジェクトを選択したのかを確認することです。残念ながら、3Dオブジェクトにマウスクリックのコールバックを設定することはできません。

まず、Viewport3Dにマウスクリックのコールバックを設定します。WPFには、マウスがどのオブジェクトをクリックしたかを確認するためのVisualTreeHelper.HitTestメソッドがありますので、このコールバックの中でHitTestを呼び出します。

<Viewport3D  MouseLeftButtonDown="OnClick" Name="myViewport3D" >

...

        // Viewport3Dクリック時のヒットテスト
        public void OnClick(object sender, System.Windows.Input.MouseButtonEventArgs args)
        {
            Point mouseposition = args.GetPosition(myViewport3D);
            PointHitTestParameters pointparams =
                  new PointHitTestParameters(mouseposition);
            VisualTreeHelper.HitTest(myViewport3D, null, HTResult, pointparams);
        }

ここで注意してほしいのは、第4引数にHitTestParameters3DではなくPointHitTestParametersを渡している点です。これは第1引数にViewport3Dを渡しているからです。Viewport3Dは3Dを2Dに投影したスクリーンですから実は2Dのオブジェクトなのです。ここに3Dオブジェクトを渡そうとすると、自前でマウスのクリック点からレイを導出し、逆座標変換でスクリーン空間からワールド空間に変換し戻す必要があるので面倒です。

HitResult関数では、次のようにHitTestResultで渡されたオブジェクトと、判定したいオブジェクトの比較を行い、必要な作業を行います。ここではVisualHitを使いましたが、RayMeshGeometry3DHitTestResultのメンバには、MeshHit、ModelHit、PointHit、VisualHitなどがあり、目的に応じて利用できます。

        public HitTestResultBehavior HTResult(System.Windows.Media.HitTestResult rawresult)
        {
            if (rawresult != null)
            {
                RayMeshGeometry3DHitTestResult rayMeshResult = 
                      rawresult as RayMeshGeometry3DHitTestResult;
               
                if (rayMeshResult != null)
                {
                    Visual3D v3d = rayMeshResult.VisualHit as Visual3D;

                    if (v3d.Equals(myModel1))
                    {
                        myPanelStoryboardForward.Begin(myViewport3D);
                    }
                    else
                    {
                        MyVideoDrawing4.Player = null;
                    }
                }
            }
            return HitTestResultBehavior.Stop;
        }

また、ここでは一番手前のオブジェクトだけを使いたかったので、HitTestResultBehavior.Stopにしていますが、HitTestResultBehavior.Continueにすれば下にあるオブジェクトのヒットテストも行います。