MoNo.Wpf.GLViewport¶
GLViewport
は MoNo.Graphics.GLViewControl
をラップしたWPFコントロールです。
- XAML にこのコントロールを埋め込むことができます。
- XAML に表示対象の
SceneGraph
を埋め込むことができます。 - 表示対象のエンティティや表示色などは Data Binding によってビューモデルと連携することができます。
- エンティティを描画するためのシーンオブジェクトをプラグイン機構によって組み込むことができます。
クラス図¶
GLViewport
と SceneGraph
およびその周辺のクラス図を下記に示します。
要点を箇条書きでまとめます。
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 の話題に入る前に、シーンのプラグインについて説明しておきます。 (プラグイン機構については プラグイン機構 を御覧ください。)
次のようなエンティティ型があるものとします。
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)
これを描画するためのシーンをプラグインによって定義するには、次のように記述します。
open System.ComponentModel.Composition
[<Export(typeof<MoNo.Graphics.ISceneFactory>)>]
type SampleEntitySceneFactory () =
inherit MoNo.Graphics.AbstractSceneFactory<SampleEntity> ()
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
オブジェクトから対応するシーンオブジェクトを得るには、次のように記述します。
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を見てみましょう。
<Window x:Class="GLViewportSample.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:GLViewportSample"
xmlns:m="http://rail.monocommunity.com"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
<m:GLViewport Background="Black">
<m:GLViewport.Lights>
<m:Light IsEnabled="True" Diffuse="Gray" Ambient="Black"
Position="1, 1, 1, 0"/>
</m:GLViewport.Lights>
<m:SceneGraph>
<m:Entry Object="{Binding Path=Scene1}"/>
<m:Entry Object="{Binding Path=Entity1}"/>
</m:SceneGraph>
</m:GLViewport>
</Window>
それではこのXAMLを順番に解説していきます。
...
xmlns:m="http://rail.monocommunity.com"
...
XML名前空間を設定し、 m
を接頭辞とすることでMoNo.RAILに定義されたクラスにアクセスできるようにしています。
...
<Window.DataContext>
<local:MainViewModel/>
</Window.DataContext>
...
ここでWindowのDataContextにサンプルプロジェクト内のMainViewModelを設定しています。 このMainViewModelについては後で見ていきます。
...
<m:GLViewport Background="Black">
<m:GLViewport.Lights>
<m:Light IsEnabled="True" Diffuse="Gray" Ambient="Black"
Position="1, 1, 1, 0"/>
</m:GLViewport.Lights>
<m:SceneGraph>
<m:Entry Object="{Binding Path=Scene1}"/>
<m:Entry Object="{Binding Path=Entity1}"/>
</m:SceneGraph>
</m:GLViewport>
...
ここで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#プロジェクトにビューモデルを作成しています。)
...
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
を見てみましょう。
...
public object Entity1
{
get { return new SampleEntity(); }
}
...
サンプルプログラムで作成した型である SampleEntiry
を返しています。
これはSampleEntity.csで実装されている、3点だけを格納するクラスで IScene
インターフェースも実装していません。
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
で作成された線分の他に、三角形が描画されていることがわかります。
つまりこの三角形は SampleEntity
を Object
に設定した Entry
がシーングラフに追加されていることにより描画されていることになります。
このシーンはどこで作成されているのでしょうか?
その秘密はこの項の最初で説明したMoNo.RAILのプラグイン機構にあります。
シーンファクトリのプラグイン¶
SampleEntity.csに定義されているもうひとつのクラスを見てみましょう。
[System.ComponentModel.Composition.Export(typeof(MoNo.Graphics.ISceneFactory))]
class SampleEntitySceneFactory : MoNo.Graphics.AbstractSceneFactory<SampleEntity>
{
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
に関連付けられたファクトリクラスがシーンを作成するコードが記述してあります。
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
メソッドが呼ばれていることがわかると思います。