.. :tocdepth:2 ============================ MoNo.Wpf.GLViewport ============================ ``GLViewport`` は ``MoNo.Graphics.GLViewControl`` をラップしたWPFコントロールです。 * XAML にこのコントロールを埋め込むことができます。 * XAML に表示対象の ``SceneGraph`` を埋め込むことができます。 * 表示対象のエンティティや表示色などは Data Binding によってビューモデルと連携することができます。 * エンティティを描画するためのシーンオブジェクトをプラグイン機構によって組み込むことができます。 クラス図 ============================= ``GLViewport`` と ``SceneGraph`` およびその周辺のクラス図を下記に示します。 .. image:: wpf_classdiagram.png 要点を箇条書きでまとめます。 * ``GLViewport`` は描画対象として ``SceneGraph`` を1つ参照します。 * シーングラフのノードはツリー構造を作ります。 ``SceneGraph`` がツリー構造のルートとなり、その下に ``Entry`` が作るツリー構造がぶら下がります。 * ``Entry`` の ``Object`` プロパティに描画対象のエンティティ(例えば直線とか円弧とかメッシュとか)が保持されます。 * ``Entry`` の ``Scene`` プロパティには、``Object`` を描画するためのシーンオブジェクトが設定されます。``Object`` が ``IScene`` インターフェイスを実装している場合には ``Object`` 自身がシーンオブジェクトとなり、そうでない場合はプラグインによって関連付けられているシーンオブジェクトが生成され設定されます。(プラグインについては後述) * ``Node`` は ``SceneGraph`` と ``Entry`` の共通部分を抽出した親クラスです。 * ``Node`` が ``Freezable`` の派生クラスとして定義されていることにより、シーングラフを XAML に埋め込んで Data Binding 機能を利用することが可能となっています。 * ほとんどのプロパティは Dependency Property として定義されており、Data Binding によってビューモデルと連携することができます。 シーンのプラグイン ============================= WPF の話題に入る前に、シーンのプラグインについて説明しておきます。 (プラグイン機構については :doc:`../misc/plugin` を御覧ください。) 次のようなエンティティ型があるものとします。 .. code-block:: fsharp type SampleEntity() = member val P1 = Point3d (1.0, 0.0, 0.0) member val P2 = Point3d (0.0, 1.0, 0.0) member val P3 = Point3d (0.0, 0.0, 1.0) これを描画するためのシーンをプラグインによって定義するには、次のように記述します。 .. code-block:: fsharp open System.ComponentModel.Composition [)>] type SampleEntitySceneFactory () = inherit MoNo.Graphics.AbstractSceneFactory () override __.Create (target, _) = let nrm = ((target.P2 - target.P1) * (target.P3 - target.P1)).Normalize() GraphicsUT.CreateScene ( MoNo.OpenGL.GLPrimType.Triangles, [| PointNormal3d (nrm, target.P1) PointNormal3d (nrm, target.P2) PointNormal3d (nrm, target.P3) |]) なお、 ``Export`` 属性を利用するには参照設定に ``System.ComponentModel.Composition.dll`` を追加する必要があります。 プラグイン機構を利用するプロジェクトでは忘れないように参照設定に追加して下さい。 ``SampleEntity`` オブジェクトから対応するシーンオブジェクトを得るには、次のように記述します。 .. code-block:: fsharp let scene = MoNo.Graphics.SceneFactory.NewInstance (SampleEntity ()) 以上のプラグイン機構を踏まえて、次のサンプルプロジェクトの説明に入っていきます。 GLViewportSample ============================= MoNo.RAIL.Samples に GLSamples/GLViewportSample というプロジェクトを用意しました。 このサンプルプロジェクトに沿って説明していきます。 参照設定 ----------------------------- サンプルプロジェクトに追加されている参照設定は以下の通りです。 * MoNo.dll * MoNo.Basics.dll * MoNo.Framework.dll * MoNo.OpenGL.dll * MoNo.Wpf.dll * System.ComponentModel.Composition.dll 特にMoNo.RAILで3D描画を扱うため、OpenGL APIをラップした ``MoNo.OpenGL.dll`` と、表示用のモジュール ``MoNo.Wpf.dll`` が必要です。 また、MoNo.RAILのモジュールではありませんが、前述したシーンのプラグインを実現するために ``System.ComponentModel.Composition.dll`` を追加する必要があります。 なお、本サンプルではF#のコードは使用していないため ``MoNo.FSharp.dll`` や ``MoNo.Framework.FSharp.dll`` は追加していません。 ビュー ----------------------------- まずはビューであるMainViewModel.xamlを見てみましょう。 .. code-block:: xml それではこのXAMLを順番に解説していきます。 .. code-block:: xml ... xmlns:m="http://rail.monocommunity.com" ... XML名前空間を設定し、 ``m`` を接頭辞とすることでMoNo.RAILに定義されたクラスにアクセスできるようにしています。 .. code-block:: xml ... ... ここでWindowのDataContextにサンプルプロジェクト内のMainViewModelを設定しています。 このMainViewModelについては後で見ていきます。 .. code-block:: xml ... ... ここでMoNo.RAILで定義されているUIコントロール ``GLViewport`` を埋め込んでいます。 ``GLViewport`` は1つの ``SceneGraph`` を埋め込むことができ、埋め込んだシーングラフを描画するためのコントロールです。 ``Lights`` プロパティには1つのライトを設定しています。 ここで設定されているライトは拡散光の色として灰色を、環境光として黒が設定されたライトで、原点から見て(1, 1, 1)の方向からの平行光源として配置しています。 ``SceneGraph`` には ``Entry`` オブジェクトをツリー状に配置することができます。また ``Entry`` クラスは ``Object`` プロパティにビューモデルの任意のオブジェクトをBindすることができます。 ここでは2つの ``Entry`` が登録されており、それぞれの ``Object`` プロパティにはビューモデルの ``Scene1`` と ``Entity1`` がBindされていることがわかります。 ビューモデル ----------------------------- では次にビューモデルであるMainViewModel.csを見てみましょう。 ビューにBindされている2つのプロパティ ``Scene1`` と ``Entity1`` だけが実装されているシンプルなビューモデルです。 (通常MoNo.RAILではビューはC#で作成し、ビューモデルはF#で記述することを推奨していますが、本サンプルでは簡便のため単一のC#プロジェクトにビューモデルを作成しています。) .. code-block:: csharp ... public MoNo.Graphics.IScene Scene1 { get { return MoNo.GraphicsUT.CreateScene( MoNo.OpenGL.GLPrimType.Lines, new[] { MoNo.Point3d.Zero, new MoNo.Point3d(1, 2, 3) }); } } ... ``Scene1`` プロパティはユーティリティクラスである ``MoNo.GraphcisUT`` の ``CreateScene`` メソッドにより ``IScene`` 実装インスタンスを返します。 ここでは原点と(1, 2, 3)を結ぶ線分を描画するシーンが作成され返されます。 ``Entry`` の ``Object`` プロパティにBindされたもうひとつのプロパティ ``Entity1`` を見てみましょう。 .. code-block:: csharp ... public object Entity1 { get { return new SampleEntity(); } } ... サンプルプログラムで作成した型である ``SampleEntiry`` を返しています。 これはSampleEntity.csで実装されている、3点だけを格納するクラスで ``IScene`` インターフェースも実装していません。 .. code-block:: csharp class SampleEntity { public Point3d P1 { get; } = new Point3d(1, 0, 0); public Point3d P2 { get; } = new Point3d(0, 1, 0); public Point3d P3 { get; } = new Point3d(0, 0, 1); } しかし、サンプルプログラムを実行すると ``Scene1`` で作成された線分の他に、三角形が描画されていることがわかります。 .. image:: sample_app.png つまりこの三角形は ``SampleEntity`` を ``Object`` に設定した ``Entry`` がシーングラフに追加されていることにより描画されていることになります。 このシーンはどこで作成されているのでしょうか? その秘密はこの項の最初で説明したMoNo.RAILのプラグイン機構にあります。 シーンファクトリのプラグイン ----------------------------- SampleEntity.csに定義されているもうひとつのクラスを見てみましょう。 .. code-block:: csharp [System.ComponentModel.Composition.Export(typeof(MoNo.Graphics.ISceneFactory))] class SampleEntitySceneFactory : MoNo.Graphics.AbstractSceneFactory { protected override IScene Create( SampleEntity target, object[] args ) { Console.WriteLine("SampleEntitySceneFacotry.Create()"); var nrm = ((target.P2 - target.P1) * (target.P3 - target.P1)).Normalize(); return GraphicsUT.CreateScene( MoNo.OpenGL.GLPrimType.Triangles, new[] { new PointNormal3d(nrm, target.P1), new PointNormal3d(nrm, target.P2), new PointNormal3d(nrm, target.P3) }); } } これが ``SampleEntity`` のためのシーンを作成するクラスです。 ``Create`` メソッドで ``SampleEntity`` を引数に取り、保持している3点から法線を持つ三角形のシーンを作成しています。 MEFの作法に従い ``[Export(typeof(MoNo.Graphics.ISceneFactory))]`` 属性をつけることにより、このクラスはシーンファクトリクラスとしてプラグインされます。 また ``MoNo.Graphics.AbstractSceneFactory`` を継承し、そのジェネリック引数で渡された型 ``SampleEntity`` に対するシーンファクトリクラスとして関連付けされます。 MoNo.RAILはシーングラフ中の ``Entry`` を辿り、そのオブジェクトが ``IScene`` でない場合、その型に関連付けられたシーンファクトリクラスからシーンを作成して描画します。 (関連付けられたシーンファクトリクラスがない場合、画面には何も描画されません。) この仕組みにより ``SampleEntity`` に対応するシーンが自動的に作成されるのです。 サンプルプロジェクト内のApp.xaml.csにも ``SampleEntity`` に関連付けられたファクトリクラスがシーンを作成するコードが記述してあります。 .. code-block:: csharp var scene = MoNo.Graphics.SceneFactory.NewInstance(new SampleEntity()); Console.WriteLine(scene.GetType()); ここで ``MoNo.Graphics.SceneFactory.NewInstance`` メソッドに ``SampleEntity`` インスタンスを渡しています。 MoNo.RAILは ``SampleEntity`` に関連付けられた ``SampleEntitySceneFacotry`` を見つけ出し、その ``Create`` メソッドを呼び出します。 試しに上記コードの ``NewInstance`` メソッドを呼び出す箇所と ``SampleEntitySceneFacotry`` の ``Create`` メソッドにブレークポイントを置いて実行してみて下さい。 ``NewInstance`` の中からプラグインされた ``SampleEntitySceneFactory`` の ``Create`` メソッドが呼ばれていることがわかると思います。