Silverlight
Silverlight 2.0 で高度な 3D アニメーションを作成する
Declan Brennan
この記事の内容 : :
- XAML の基礎
- XAML で要素を作成する
- 多面体を組み立てる方法
- DirectX 数学ライブラリをエミュレートする
|
この記事は次のテクノロジを使用しています:
Silverlight
|

コンテンツ
何か信じられない不運によって、SilverlightTM に関するこの数か月間の発表をまったく耳にされていない方のために、ここで最新の情報をお伝えしておきます。Silverlight は、Microsoft から提供される新しいクロスブラウザ プラグインであり、以前は Flash や Java アプレットが独占していた領域に、Microsoft® .NET Framework の能力を取り入れるものです。Silverlight には、すぐに使える便利な機能が豊富に組み込まれています。.NET Framework 3.5 の簡易版がサポートされ、これには XML と XAML (Extensible Application Markup Language)、ジェネリック コレクション、Web サービス、LINQ などが含まれています。Silverlight はいくつかの .NET 互換言語もサポートしていますが、ここでは C# だけを取り上げます。
新しいテクノロジを習得するための最善の方法は、多少の楽しみを交えることだと思います。そこで私は、Silverlight 1.1 のアルファ版がリリースされ、ダブリンの IMT カンファレンスで Tim Sneath による興味深いプレゼンテーションを見たときに、教育用のちょっとしたアプリケーションを作成することを決めました。それは、平面のテンプレートを折りたたんでさまざまな 3D 図形 (多面体) を組み立てるようすを表示するというものです。Silverlight は既定では 3D をサポートしていないため、3D 処理を行う DirectX® 数学ライブラリのエミュレーションを構築する必要がありました。
多面体は、平らな面から構成される 3 次元オブジェクトです。この Silverlight サンプルで扱っているのは正多面体および半正多面体であり、それぞれプラトン多面体およびアルキメデス多面体とも呼ばれます。これらの多面体の面はすべて正多角形であり、たとえば正三角形や正方形のように、面の各辺がすべて同じ長さです。また、これらは凸形であり、突き出した部分を持ちません。古代ギリシャ人の名前が付いていることからもわかるとおり、これらの物体は長い間にわたって人類を魅了してきました。興味のある方は、George Hart の Web サイトでそれらについて詳しく知ることができます (
georgehart.com/virtual-polyhedra/vp.html)。
完成したアプリケーションのデモを
図 1 に示しています。また、
picturespice.com/ps/Polyhedra/ClientBin/TestPage.html でも見ることができます。基本的に、このアプリケーションではマウスを使って 1 つの図形 (多面体) を選択できます。すると、ウィンドウの右上隅に選択した図形の情報が表示され、選択した多面体を形成するために平面のテンプレートを折りたたむようすがアニメーションで表示されます。最後に、[循環] ボタンをクリックすると、各図形に対して順番に同様なアニメーションが表示されます。
Figure 1 Silverlight Demonstration of Polyhedra (画像を拡大するには、ここをクリックします)
XAML を使用する
他の多くの Silverlight アプリケーションと同じように、Polyhedra は XAML コードを多数使用しています。XAML はコンテンツ定義言語の 1 つで、HTML と似ていますが、より高い柔軟性を持っています。ここで、HTML との類似点について考えます。HTML ページを作成する場合、HTML ドキュメント オブジェクト モデル (DOM) だけを使用して作成することも可能ですが、コンテンツを作成するのに賢明な方法であるとは言い難く、多くの場合は、コーディングに時間がかかるだけでなく、初期化の遅いページが生成されます。ほとんどの場合における最良の方法は、ページ内のできるだけ多くの部分を HTML マークアップとしたまま、柔軟性が必要な箇所では JavaScript や DOM を使用して機能を強化することです。
XAML の場合も、これと非常によく似たアプローチが採用されます。コンテンツを作成する最も早い方法は、できるだけ多くの箇所に XAML マークアップを使用しながら、必要に応じて C# や Silverlight Media API などの .NET 準拠言語を使用して機能を強化することです。XAML は、ハンド コーディングすることも、Expression BlendTM などのデザイン パッケージによって生成することもできます。さらには、開発プロセス中に実行するプログラムから生成したり、サーバー上で動的に生成したりもできます。これには、ある程度考え方を変える必要があるかもしれません。C# プログラマにとってはすべてが簡単すぎるため、XAML に任せるのが最良である機能も自分が慣れた環境でコーディングしてしまいがちです。
ここでは、XAML についてごく簡単に紹介するにとどめておきます。XAML については、Charles Petzold 氏の著書『Applications= Code+Markup』で詳しく説明されています。
次に、新しい言語を学習するときにはだれもが目にする、"Hello World" サンプルの XAML 版を示します。
<UserControl x:Class="Polyhedra.Page"
xmlns="http://schemas.microsoft.com/client/2007"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Width="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock>Hello World</TextBlock>
</Grid>
</UserControl>
ルート要素は、UserControl です。これには Grid が含まれ、さらに Grid には、"Hello World" テキストを含む TextBlock 要素が含まれています。
UserControl の属性を簡単に見てみましょう。あまり詳しくは説明しませんが、これは XAML の分離コード クラスに相当するものを定義しています。このクラスは、XAML の解析および読み込みと同時にインスタンス化されます。初期化のさまざまな部分をコンストラクタで実行できますが、より細かな動作を得るには、多くの場合、イベント ハンドラを使用する必要があります。これは、Silverlight の主要な機能の 1 つです。イベント ハンドラは、各種の XAML オブジェクトにアタッチでき、好みの .NET 準拠言語で実装できます。JavaScript とは異なり、コンパイル型言語であるため、そのような言語でしか実現できないさまざまな可能性が開かれています。
HTML との類似点に戻ると、要素は、ページ上での位置を調整するためにさまざまな DIV 内にグループ化されることがよくあります。同様に、XAML では、図形が Canvas 内に (または Grid など、Canvas の一種である他の要素内に) グループ化されます。HTML で DIV が入れ子になることが多いように、Canvas も XAML 内で入れ子にできます。HTML では、ほとんどの要素が長方形です。一方、XAML では、TextBlock、Rectangle、Polygon、Ellipse などあらゆる種類の図形と、ユーザー定義図形を使用するための非常に柔軟な Path をサポートしています。HTML の要素は、ID 属性で識別されます。それに相当する、XAML 内の要素を識別する属性は x:Name です。ここで x は、XAML 名前空間のエイリアスです。
XAML では、単に静的なページを生成するだけでなく、さらに多くのことが可能です。最も強力な機能の 1 つは、Storyboard を使用して、XAML で指定された初期 UI への変更をアニメーション化することです (Internet Explorer® 5.0 では、それに似た HTML+Time と呼ばれる機能が HTML に追加されています)。たとえば、オブジェクトの色、可視性、または透明度に対する変更をアニメーション化できます。変換と組み合わせることで、Storyboard ではオブジェクトの回転、拡大/縮小、および移動ができます。
Storyboard は、従来のコードをほとんどまたはまったく使用せずに、あらゆる種類のアニメーション効果を生成できます。トリガと組み合わせて使用すれば、理論的には、あるオブジェクトに対して MouseEnter などのイベントが発生したときに、各種のアニメーションを自動的に開始することが可能です。ただし、2008 年 3 月の Silverlight 2.0 Beta の時点では、トリガで処理されるイベントは Loaded だけです。他のイベントに対しては、イベント ハンドラの形で少量のプラミング コードが必要となります。この記事を読者の皆さんが読まれている時点ではどうなっているかわかりませんが、この状況は近いうちに変わるでしょう。
Silverlight の Storyboard が持つ優れた機能の 1 つは、動作がフレームベースではなく時間ベースであるということです。異なる時間やイベントに結び付けられた複数の独立した Storyboard を使用することで、複雑な動作を単純に実装できるようになります。結局のところ、実世界はフレーム単位で動いているわけではありません。フレームベースの処理は、動画をセルロイドで実現していた時代の遺物です。それぞれ独立したオブジェクトが独立した動作で実装されれば、人生はずっと単純になるでしょう。
XAML のいくつかの例
Polyhedra アプリケーションの核心である折りたたみアニメーションは、C# コードを使用して生成されます。ただし、残りの大部分は XAML で定義されています。これには、サンプル多面体が並んだ円の表示、現在選択されている多面体をさまざまな方法で強調する機能、および [循環] ボタンが有効になっていることを示す回転する矢印のアニメーションなどが含まれます。
Page.xaml からのいくつかの抜粋で、これらのアニメーションがどのように実現されているかを見てみましょう。最初に、図 2 に示される [循環] ボタンのコードを見ます。ここには、Cycle という名前の Path があります。データ属性は一連の動作を指定し、M は移動、A は円弧、L は直線です。これはかなり単純なグリフであるため、単に使用する図形を紙に描いて、必要な動作を手動で書き出しました。ほとんどの場合は、Expression Design などのツールを使用した方がよいでしょう。

Figure 2 [循環] ボタン
<Canvas x:Name="CycleButton"
Canvas.Top="250"
MouseLeftButtonDown="CycleButtonLeftMouseDown" >
<Path x:Name="Cycle" Stroke="#000033"
Fill="#FFB47C0D" Canvas.Left="10" Canvas.Top="10"
Data="M 25,35 A 10,10 180 1 0 25,15 L 25,20 L 12.5,10 L 25,0 L 25,
5 A 10,10 180 1 1 25,45 Z"
Width="50" Height="50">
<Path.RenderTransform>
<RotateTransform x:Name="CycleRotate" Angle="0"
CenterX="25" CenterY="25"/>
</Path.RenderTransform>
<Path.Resources>
<Storyboard x:Name="CycleLatched">
<DoubleAnimation Storyboard.TargetName="CycleRotate"
Storyboard.TargetProperty="Angle" From="360" To="0"
Duration="00:00:02" RepeatBehavior="Forever"/>
</Storyboard>
</Path.Resources>
</Path>
<TextBlock x:Name="CycleCaption" Canvas.Left="65" Canvas.Top="10"
Foreground="#FFB47C0D" FontSize="30" FontWeight="Bold"
Text="Cycle" />
<Rectangle Width="200" Height="70" RadiusX="30" RadiusY="30"
Stroke="#FFB47C0D" StrokeThickness="4" Fill="Transparent"/>
</Canvas>
この Path には、CycleRotate という名前の RotateTransform もあります。最初、この変換は、角度が 0 に設定されているため、何もしません。ただし、CycleLatched という名前の Storyboard があり、これはアクティブになると、角度を小さな一定の値ずつ連続的に増加させながら、矢印を回転させます。
図 3 に、1 つの多面体サンプルがどのように定義されているかを示します。この XAML の終わりの部分を見ると、このサンプルが、四面体を定義する 4 つの多角形から構成されていることがわかります。これらは、Model0 という名前の専用の Canvas 内にあり、最初は不可視である Ring0 という名前のリング (Ellipse) で囲まれています。2 つの Storyboard アニメーションが定義され、1 つは MouseEnter 用、もう 1 つは MouseLeave 用です。MouseEnter アニメーションは、リングを直ちに可視状態にして、Model0 Canvas のサイズを拡大し、0.7 秒間だけ不透明度を高めます。MouseLeave アニメーションは、それらと逆の変化を行います。

Figure 3 Polyhedron サンプル
<Canvas x:Name="Canvas0" Width="116.376" Height="116.376"
Canvas.Left="671.812" Canvas.Top="341.812"
MouseEnter="TriggerMouseEnter" MouseLeave="TriggerMouseLeave">
<Canvas.Resources>
<Storyboard x:Name="Canvas0MouseEnter">
<DoubleAnimation Duration="00:00:00" Storyboard.TargetName="Ring0"
Storyboard.TargetProperty="Opacity" From="0.0" To="1.0" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName="Model0"
Storyboard.TargetProperty="Opacity" From="0.7" To="1.0" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName="Model0Scale"
Storyboard.TargetProperty="ScaleX" From="0.7" To="1.0" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName="Model0Scale"
Storyboard.TargetProperty="ScaleY" From="0.7" To="1.0" />
</Storyboard>
<Storyboard x:Name="Canvas0MouseLeave">
<DoubleAnimation Duration="00:00:00" Storyboard.TargetName="Ring0"
Storyboard.TargetProperty="Opacity" From="1.0" To="0.0" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName="Model0"
Storyboard.TargetProperty="Opacity" From="1.0" To="0.7" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName="Model0Scale"
Storyboard.TargetProperty="ScaleX" From="1.0" To="0.7" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName="Model0Scale"
Storyboard.TargetProperty="ScaleY" From="1.0" To="0.7" />
</Storyboard>
</Canvas.Resources>
<Canvas x:Name="Model0" Opacity="0.7"
Width="116.376" Height="116.376" >
<Canvas.RenderTransform>
<ScaleTransform x:Name="Model0Scale" CenterX="58.188"
CenterY="58.188" ScaleX="0.7" ScaleY="0.7"/>
</Canvas.RenderTransform>
<Polygon Canvas.ZIndex="-12545653" Fill="#FFCC0000"
Stroke="#000000" StrokeThickness="1"
Points="97.566,74.278 43.052,40.971 33.836,99.937 "/>
<Polygon Canvas.ZIndex="-11948057" Fill="#FFCC0000"
Stroke="#000000" StrokeThickness="1"
Points="43.052,40.971 97.566,74.278 58.188,37.662 "/>
<Polygon Canvas.ZIndex="-11309683" Fill="#FFCC0000"
Stroke="#000000" StrokeThickness="1"
Points="33.836,99.937 43.052,40.971 58.188,37.662 "/>
<Polygon Canvas.ZIndex="-10481036" Fill="#FFCC0000"
Stroke="#000000" StrokeThickness="1"
Points="97.566,74.278 33.836,99.937 58.188,37.662 "/>
</Canvas>
<Ellipse x:Name="Ring0" Opacity="0" Stroke="#FFB47C0D"
StrokeThickness="4" Width="116.376" Height="116.376" />
</Canvas>
これらの Storyboard はそれぞれ独立して動作できるため、ユーザーがどれだけ速くマウスを動かしても、すべてが期待どおりに動作します。一般に、1 つのサンプルに対する MouseLeave アニメーションは、新しく選択されたサンプルに対する MouseEnter アニメーションと同時に実行されます。ただし、Silverlight は複数のアクティブな Storyboard を自動的に並行して処理するため、その点について特に気にかける必要はありません。
余談ですが、四面体サンプルの 4 つの多角形のそれぞれの頂点をどのように計算したのか疑問に思われているかもしれません。アプリケーション内の他の各サンプルでは、これよりずっと多数の多角形が使用されていますから、なおさらです。私は、コンピュータの方が速くできることは自分ではやらない、という怠惰なアプローチを強く信奉しています。したがって、私がしたことは、実際のプロセスでは Polyhedra の改定版を使ったということだけです。このバージョンでは、各サンプルについて中心のアニメーションの 1 フレームを順番に実行しました。これは、完全に閉じた図形のフレームです。結果の Polygon グループをそれぞれ、円の周囲に等間隔で並んだ Canvas のセットに配置し、メイン プログラムで使用するためにその全体を 1 つのファイルに格納しました。
オブジェクトを円の周囲に配置するには、角度を 2*PI/NumSamples の等間隔で刻み、x = Radius*Cos(Angle) および y = Radius*Sin(Angle) の座標で各サンプルの中心を指定する必要があります。また、オブジェクトを配置する Canvas は Left および Top プロパティを使用するため、中心をそれぞれ幅の 1/2 と高さの 1/2 だけオフセットする必要がありました。
XAML を使用するためのヒント
既にお気づきのとおり、XAML ではコードをほとんど追加せずに非常にリッチな UI を実現することが可能です。Page.xaml のような大きなファイルに対しても、初期化は驚くほど短時間で済みます。話を先に進める前に、Silverlight 2.0 の現在の実装 (March 2008 Beta) での私の経験に基づいて、いくつかのヒントを示しておきましょう。
既に述べたとおり、現在のところ、トリガは Loaded イベントに対してだけ、Storyboard を自動的に (Begin メソッドを介して) 開始できます。他のイベントに対しては、次のようなイベント ハンドラの形で少量のプラミング コードが必要となります。
public void MouseEnterHandler(
object o, EventArgs e) {
this.MouseEnterStoryBoard.Begin();
}
Storyboard に対して、オブジェクト名の後にイベント名が続くなど、何らかの命名規則を適用すれば、コーディングが必要な個別のイベント ハンドラの数を大きく減らすことができます。たとえば、Polyhedra では、円内のすべてのサンプルに対する共有イベント ハンドラとして次のメソッドが使用されています。
public void MouseEnterHandler(
object o, EventArgs e) {
this.triggerStoryboard(o,"MouseEnter");
}
private bool triggerStoryboard(
object o, string eventType) {
Canvas el = o as Canvas;
string name= el.GetValue(NameProperty) as String;
Storyboard sb = el.FindName(name + eventType) as Storyboard;
if (sb != null)
sb.Begin();
return (sb != null);
}
Silverlight 2.0 Beta では、メインの初期化 (InitComponents を呼び出す) が Loaded イベント ハンドラから分離コード オブジェクトのコンストラクタに移動されています。これはより洗練されていますが、コンストラクタですべてが可能なわけではないことに注意してください。たとえば、ここでは Storyboard 上で Begin または Pause を呼び出すことができないため、それはやはりイベント ハンドラで実行する必要があります。
Andy Beaulieu 氏が作成した、小惑星を爆破する Silverlight Rocks! というサンプル (
www.andybeaulieu.com/Home/tabid/67/EntryID/73/default.aspx) から発見したことですが、コードベースのアニメーションを実現する優れた方法の 1 つは、短い時間に設定した Storyboard を使用しながら、Completed イベント ハンドラを使用して、アニメーションの 1 フレームの実行後に Storyboard を再起動することです。
public Page() { // Constructor for "code-behind"
// Required to initialize variables
InitializeComponent();
this.animationTimer.Completed +=
new EventHandler(animationTimer_Completed);
}
void animationTimer_Completed(object sender, EventArgs e) {
[ Do a frame of animation ]
this.animationTimer.Begin();
}
Silverlight 2.0 Alpha の September Refresh では、Storyboard の要件が変更されたため、アニメーションには、使用しない場合でもターゲットが必要となっています。
<Canvas.Resources>
<Storyboard x:Name="animationTimer">
<DoubleAnimation Duration="00:00:00.01"
Storyboard.TargetName="bogusTimerTarget"
Storyboard.TargetProperty="Width" />
</Storyboard>
</Canvas.Resources>
<Canvas Name="bogusTimerTarget">
</Canvas>
同じ HTML ページ上に個別の Silverlight コントロールを多数配置することは避けてください。Polyhedra の最初の実装では、円内の各サンプルに対して別個のコントロールを使用していたため、メモリをかなり大量に消費しました。これは、場合に応じて、HTML から XAML にコンテンツを移動して、使用するコントロール数を減らす必要があることを意味します。
XAML の利点の 1 つは、UI 内の多くの定型処理のための作業負荷が減り、創造的な問題領域のコードに集中できるようになることです。この例での問題領域は、3D 図形を形成するためのテンプレートの折りたたみであり、これは次のセクションで説明します。
多面体を組み立てる方法
前述した Charles Petzold 氏の著書は、それよりもずっと古い、オブジェクト指向以前に Niklaus Wirth 氏によって書かれた『アルゴリズム + データ構造 = プログラム』という本の翻案ではないかと私は考えています。この本は、長い年月が過ぎても、私がこれまでに読んだ中で最も影響を受けた本の 1 つであり、その後言語やパラダイムにさまざまな変化があったにもかかわらず、今でも十分に役立つ内容を含んでいます。この本の基本的なテーマは、どのようなデータ構造が問題を最もよくモデル化できるかを特定し、それらのデータ構造をどのようなアルゴリズムで処理または変更できるかを特定するという、開発アプローチです。これは、標準的でない問題に取り組む際に、私がよく採用するアプローチです。
最終的に Polyhedra で使用したアプローチに落ち着くまでには、さまざまなアプローチを試してみました。折りたたみアニメーションを実現するために、どれだけ少ない情報から実際にスタートできるかを知りたかったからです。その結果わかったことは、すべての辺が同じ長さであるため、与えられた多面体でどの面がどの面と接続しているかを知るだけで、ほとんどすべてが可能になるということです。これは、グラフ データ構造を暗示しています。最終的な XAML 出力に至る前に、一連のアルゴリズムを使用して、2 つの個別のツリー データ構造でグラフを処理しています。それらについてはすべて、後で説明します。
Windows® Presentation Foundation (WPF) は XAML で 3D をサポートできますが、Silverlight は既定では 2D しかサポートしません。これは、マシン内にどのようなグラフィック プロセッサ ユニット (GPU) ハードウェアが含まれているかを気にしなくて済む方が、ブラウザ間の互換性をずっと簡単に実現できるからです。もちろん、突き詰めて考えれば、コンピュータ画面上の 3D は目の錯覚にすぎません。どのような操作が行われているにしろ、結局、モニタ画面に表示されているのは通常の 2D 多角形の集まりです。独自のコードで 3D 操作を実行してこれらの多角形の座標を計算する手間をかけるならば、ハードウェア アクセラレータを使用しない 3D レンダリングも可能です。数十個のポリゴンに限定すれば、パフォーマンスも許容範囲内です (主観的には、フレームのパフォーマンスは Alpha から Beta への移行により向上しています)。
図形が選択されると、Net という名前 (.NET とは無関係) の、展開された 2 次元テンプレートが作成されます。この作業は、図 4 に示すように、2 段階で行われます。最初に、図形の各面に 1 つの GraphNode が対応するように、メモリ内にグラフが作成されます (Graph.cs を参照)。このグラフは、アプリケーション アセンブリに埋め込まれた .shp リソースの 1 つから接続性情報 (どの面がどの面に接続されているかを示す) のセットを取り込むことで作成されます。たとえば、cube.shp の内容は次のようになります。
Figure 4 Building the Shape, from Graph to XAML (画像を拡大するには、ここをクリックします)
1:3,4,5,2
2:1,5,6,3
3:2,6,4,1
4:3,6,5,1
5:1,4,6,2
6:2,5,4,3
次に、グラフ内の任意の GraphNode を、Net の作成を開始する最初のノードとして選択します (詳細については、コード ダウンロードの Net.cs を参照してください)。そして、GraphNode の隣接数と同じ数の辺を持つ 2 次元 FlatFace を配置します。さらに、任意の隣接する GraphNode を選んで同じ処理を繰り返し、現在の FlatFace と辺を共有する次の FlatFace を配置します。各 GraphNode を 1 回だけ選択し、すべての GraphNode が選択されるまで処理を続けると、FlatFace のツリー構造が構築されます。
3D アニメーションの各フレームで、Net から 3 次元多面体 (おそらく部分的に閉じている) が作成されます (この操作の詳細については、この記事で後述する Polyhedron.cs を参照してください)。これには、FlatFace のツリーを Face のツリーにコピーして、2D 頂点を 3D の点で置き換える作業が含まれます (図 5 に、この 2 つの座標系を示しています。水平面から開始するために、Y はゼロから開始する必要があります。そのため、X = X、Y = 0、および Z = Y に設定することで、2D を 3D にマッピングしています)。
Figure 5 Transforming Points from 2D to 3D (画像を拡大するには、ここをクリックします)
最後に、再帰処理により、Face のツリー内の各接合点を順に回って、折りたたみを実行します。折りたたみの量は、図形が完全に閉じているときに各面が互いに離れている角度 (二面角) を分割した量です。理論的には、.shp ファイル内の接続性情報だけを使用して、二面角を直接計算することが可能です。
これは、3 つの面が頂点で接していたり、任意の数の同じ種類の面が頂点で接していたりするような、特別なケースでは、かなり簡単です。ただし、一般的なケースではより難しくなるため、これらの角度を個別の .dihedrals リソースに格納することしました。たとえば、cube.dihedrals からの抜粋を次に示します。
1,3:1.5707965056551
1,4:1.57079661280793
1,5:1.57079614793469
1,2:1.57079604078186
2,1:1.57079604078186
2,5:1.57079628466395
2,6:1.57079661280793
2,3:1.57079636892585
3,2:1.57079636892585
3,6:1.57079614793469
3,4:1.57079628466395
...
これらの数の間の微小なばらつきは、単に計算方法に起因する誤差です (この場合は、本来すべてπ/2 に等しくなります)。
最終的なプロセスには、多面体のビューを多角形の集まりとしてモニタに投影するための、一連の変換が含まれます。お気づきかもしれませんが、折りたたみアニメーションの表示中、多面体は垂直軸の周りを回転しています。これを実現するために、回転中心を原点に変換し、回転を実行した後で、視点に変換しています。その後、透視変換を行って、(Z 軸上で) 遠くにある物体が小さく見えるようにしています (次のセクションで、変換の方法についてもう少し詳しく説明します)。
最後に、モニタ画面への 3D 図形の投影に対応して、XAML 多角形のセットが生成されます。Silverlight は、3D 多角形の中心の Z 座標 (float から int に変換) を使用して各多角形の XAML ZIndex プロパティを設定することで、これらの多角形を後ろから前への順序で描画します。これはかなり単純化された 3D の実現方法であり、たとえば各多角形が互いに交差していない (このサンプルではそれを回避できていますが) など、理想的な条件でのみ機能します。より洗練された形態の 3D では、奥行きバッファなどのメカニズムが必要となります。それには GPU アクセスが必要であるため、Silverlight .NET サンドボックスの範囲からは外れます。
最後の仕上げとして、XAML では (Opacity プロパティを使用して) 多角形の一部を透明にすることができ、それにより多面体が部分的に透けて見える芸術的効果が得られます。必要な作業は以上です。
DirectX 数学ライブラリをエミュレートする
ここで、アニメーションの実現に使用されている数学について軽く触れておきます。より詳しく知りたい場合は、wikipedia.com、euclideanspace.com、mathworld.wolfram.com、gamasutra.com、gamedev.net など、多くの役に立つ Web サイトがあります。
前述の XAML サンプルで、既に RotationTransform や ScaleTransform などいくつかの 2D 変換を紹介しています。これらのサンプルでは、Silverlight がすべての作業をやってくれます。XAML を WPF 環境で使用する場合は、3D 変換にもアクセスできますが、Silverlight では 3D 変換を使用できません。WPF では、そのための低レベル操作に DirectX を利用しています。DirectX には 3D 変換に必要な数学演算を行う優れたクラスのセットが用意されているので、それは理にかなっています。Silverlight で同じ変換のいくつかを行うために、私はこれらのクラスの多くに対して DirectX 数学ライブラリのエミュレーションを作成し、Silverlight サンドボックスで使用できるようにしました。
Direct3D® でのコーディング経験をお持ちの方であれば、Vector2、Vector3、Matrix などの Microsoft 実装についてはよくご存知かと思われます (図 6 を参照)。それ以外の方のために、厳密ではありませんが非常に簡単な概要と、いくつかの例を示します。
Figure 6 Emulated DirectX Math Classes (画像を拡大するには、ここをクリックします)
2D で点を特定するには、X と Y の 2 つの座標が必要です。3D では、X、Y、および Z の 3 つの座標が必要です。厳密に言えば点はベクトルではありませんが、2D の点は Vector2 オブジェクトに格納でき、3D の点は Vector3 オブジェクトに格納できます (ベクトルは実際には大きさと方向を持つ存在であり、原点から始まりその点で終わる矢印として表すことができます。ある意味、すべてのベクトルは原点から始まると考えることができます)。
3 次元の図形は、面の集合として識別できます。そして面は、頂点の集合として識別されます。実行する必要がある操作は、ほとんどすべて、回転、変換 (または移動)、拡大/縮小などのいくつかの操作によって、これらの点を別の点の集合に変換する操作を含んでいます。必要な操作のほとんどすべてを、行列と呼ばれる 4 × 4 個の数字のグリッドで表すことができます (一般に行列は、実際には任意の数の行および列を持つことができますが、ここでは、同次座標と呼ばれる座標による 3D 点の操作に使用する特別な行列だけを取り上げます)。これらの操作は、適用されるオブジェクトの形状を (過度には) ゆがめないため、線形変換と呼ばれます。
非常に便利なことに、そのような 2 つの行列を、同じ効果を持つ 1 つの行列に結合する方法があります。この演算は行列乗算と呼ばれ、何回か繰り返すことができます。これは、一連の変換全体をまとめて、同じ効果を持つ 1 つの行列に置き換えられることを意味します。この方法は、各変換を別々に適用するよりもずっと効率的です。行列乗算がどのように実装されるのか、または行列乗算がそのような変換マジックをどのように行うのかを気にする必要はありません。それは単にツールとして利用します。
元の点を目標の点に変換するために、行列とベクトル オブジェクトとの間で定義されている乗算もあります。(行列間の乗算との) 混乱を避けるために、これは TransformCoordinate メソッドを使用して Vector3 に実装されています。
いくつかの小さなコード スニペットで、これをよりわかりやすく示します。最初に、polyhedron.cs 内の次のコードを使用して、2 つの面をその共通の辺に沿って折りたたみます。
Vector3 axis = axisTo - axisFrom;
Matrix foldTransform =
Matrix.Translation(-axisFrom) *
Matrix.RotationAxis(axis, proportion * _dihedralAngle) *
Matrix.Translation(axisFrom);
Vector3[] p= Vector3.TransformCoordinate(
face.Points,foldTransform);
ここで、共通の辺は、axisFrom から axisTo までの直線として定義されています。回転は常に原点を通る直線の周りで定義されるため、回転を行う前に、辺の 1 つの点を原点に移動し、後でそれを元の位置に戻す必要があります。そのために、3 つの線形変換を結合して、面座標の変換に使用する行列を作成しています。3 つの線形変換とは、原点への移動、回転、および元の位置への移動です。
ここで、もう 1 つ別の小さな仕掛けがあります。回転軸を表すベクトルも原点から始まる必要があるため、axisFrom を減算して、(axisFrom, axisTo) を (origin, axisTo-AxisFrom) に移動しています。
まだ頭が数学で満杯になっていないようでしたら、もう 1 つの例 (projector.cs に基づく) を試してみましょう。これは、多面体を構成する多角形を、画面の視点から見たときの状態に変換するために使用します。
Matrix projection =
Matrix.Translation(-pivot) *
Matrix.RotationY(yaw) *
Matrix.RotationX(angle) *
Matrix.Translation(viewPoint) *
Geometry.Perspective(5);
Vector3[] p= Vector3.TransformCoordinate(face.Points,foldTransform);
折りたたまれている間、図形は水平面内で回転されることを思い出してください。これを行うために、回転軸上の点を原点に移動してから、垂直 (Y) 軸の周りにヨー角度だけ回転します (実際に、またはシミュレーションで飛行機を飛ばした場合、ヨーに加えてピッチおよびロールと呼ばれる 2 つの独立した回転が加わる可能性があります)。
ここでは、開始位置に戻る代わりに、視点への移動を行っています。最後に、透視変換を行って、Z 軸上で遠くにある物体を小さくしています。ここでは多くのことが行われていますが、すべてを 1 つの 4 × 4 行列に結合することができるのが非常に印象的です。
さらなる応用
Polyhedra では使用していませんが、エミュレーション ライブラリでは四元数も使用できます。これは、複数の 3D 回転を 1 つの回転に結合するための優れた方法です。また、1 つの向きから別の向きへとスムーズに変更するのにも有用です。四元数は、Sir William Rowan Hamilton 氏 (私の故郷であるアイルランドのダブリン出身) が街を歩いている間に発見したもので、彼は忘れないようにすぐブルーム橋の側面に数式を書き刻んだそうです (
www.maths.tcd.ie/pub/HistMath/People/Hamilton/Quaternions.html を参照)。私の落書きもすべて同じように学問的なものだと主張したいですが、おそらく信じてはもらえないでしょう。
また、四元数を使用すると、ジンバル ロックと呼ばれる問題を回避できます。これは、アポロ月計画でのジャイロスコープの操作で重要な役割を演じ、アポロ 13 号の任務を描いた映画でも取り上げられました。
ベクトルの内積や外積など、ここで取り上げていない事項はまだ他にもたくさんありますが、DirectX の数学ライブラリのエミュレーションで何が可能になるのか、感じだけでもつかんでいただけたかと思います。もともとこのエミュレーションは、ASP.NET が動作する、DirectX をインストールできないホストされたサーバー上で変換を実行するために開発したものです。このような種類の環境では、他の応用方法も見つかるかもしれません。
ユーザーの興味を引き付け、すばやく、印象的な方法で情報を伝えることができるような Web UI を提供するために、Silverlight がどのようにして便利で拡張可能なツールを提供するのか、Polyhedron によってイメージしていただけたらさいわいです。過去の .NET の経験に基づいて、さまざまな種類の創造的な方法でアプリケーションを構築できます。JavaScript とは異なり、クライアントで実行されるコードの行数にとらわれる必要はありません。
Declan Brennan は、世界最初のマイクロプロセッサについて覚えているほどのベテランで、どんな願いもかなえてくれる精霊がそばにいるという状況にはまだ慣れていません。彼は、自分の住む世界が、テクノロジによって制約されることのない、ただ想像力の豊かさだけが左右する世界であるという幸運を、信じることができません。詳細については、
declan.brennan.name を参照してください。