(以下選自南開大學(xué)出版社出版的《WPF和Silverlight教程》)
3Dmax中的建模模型可以導(dǎo)出為obj文件格式,將此文件導(dǎo)入WPF項目中,由WPF完成對三維造型的貼圖和控制設(shè)計。本例在3Dmax中設(shè)計了1個雙翼開瓶器模型,將“開瓶器.obj”和貼圖材質(zhì)文件都添加到項目中(“素材”文件夾)。圖2-206 的左側(cè)是“開瓶器.obj”文件拖入到【設(shè)計面板】后,在【對象和時間線】面板中看到的結(jié)構(gòu),右側(cè)是貼圖后的開瓶器模型,中間是本例完成的對開瓶器部件進行拆卸和裝配的控制按鈕。下面說明設(shè)計過程。
1.obj文件導(dǎo)入后的對象
圖2-206左側(cè)看到的是obj文件導(dǎo)入后的對象結(jié)構(gòu),ViewPort3D(命名為viewport3)是三維對象的容器,其中包含相機元素Camera(ModelVisual3D),默認相機是透視相機PerspectiveCamera(已經(jīng)命名為ppc);World元素(ModelVisual3D)中包含了環(huán)境光子元素AmbientLightContainer(ModelVisual3D)、方向光子元素DirectionalLightContainer(ModelVisual3D)和三維造型子元素RootGeometryContainer(ModelVisual3D),后者又包含了qua02(3Dmax中的原名)等6個ModelVisual3D子元素,每個子元素包含造型元素材質(zhì)設(shè)置DefaultMaterial(GeometryModel3D)。

圖2-206
開瓶器結(jié)構(gòu)、外觀和控制按鈕
表2-1三維造型元素名稱(3Dmax中的原名)和開瓶器部件名稱對照
三維造型元素名
|
開瓶器部件名
|
三維造型元素名
|
開瓶器部件名
|
qua01
|
左翼
|
Object
|
開瓶器座
|
qua02
|
右翼
|
Object03
|
左翼螺釘
|
pemo
|
開瓶器把手
|
Object04
|
右翼螺釘
|
2. 三維造型元素初始位置
obj文件拖入Window3.xaml【設(shè)計面板】后,盡量發(fā)大到和設(shè)計窗口界面一樣大小,這時的大小不一定合適,可以調(diào)節(jié)的照相機初始位置,如圖2-207左圖。其中參數(shù)是調(diào)整后的參數(shù),比如Position,在obj文件剛導(dǎo)入時,X、Y和Z不一定是目前的數(shù)值,改變Z參數(shù)的數(shù)值可以調(diào)節(jié)三維造型在屏幕中的大小。Direction參數(shù)中的X、Y調(diào)節(jié)到0,相機面向Z坐標軸的負方向。本例中Far
Clipping Plane的參數(shù)調(diào)節(jié)的比較大,當(dāng)3D對象縮小的很小時還能完整看到造型全貌。

圖2-207
照相機初始位置參數(shù)設(shè)置和World初始變換設(shè)置
另外,“World”元素做了位移變換,見圖2-207右圖,Z坐標使造型大小改變,Y坐標產(chǎn)生上下位移。所有三維造型元素的其他變換參數(shù)默認是0,知道這些參數(shù)的初始值對后面的故事板設(shè)計很重要。
3. 貼圖和光線設(shè)置
貼圖需要的材質(zhì)圖片“金屬7.jpg”和“外殼.jpg”已經(jīng)添加到項目的“素材”文件夾中,將這些圖片拖入【設(shè)計面板】后生成畫刷資源,保存到ResourceDictionary1.xaml資源文件中,用于三維造型元素的材質(zhì)貼圖。“Object”元素(“開瓶器座”)的“DefaultMaterial”貼圖使用了“外殼.jpg”生成的畫刷資源,“螺釘”沒有貼圖,采用“白色”材質(zhì),其余采用“金屬7.jpg”
生成的畫刷資源,貼圖過程略。
環(huán)境光“AmbientLight”采用定向光,設(shè)置為白色,方向光DirectionalLight也采用定向光,設(shè)置為白色,調(diào)整初始角度可以明亮照射3D對象。
4. 開瓶器旋轉(zhuǎn)故事板設(shè)計
示例中設(shè)計了1個故事板StoryBoard0(程序中的命名為mystoryboard0),用于實現(xiàn)整個造型的三維空間旋轉(zhuǎn),如圖2-208所示。

圖2-208 電動機三維空間旋轉(zhuǎn)故事板設(shè)計
故事板StoryBoard0針對三維元素“World”和方向光DirectionalLight設(shè)置了動畫,故事板中有5個關(guān)鍵幀,“World”圍繞Y軸進行旋轉(zhuǎn)變換(參考圖2-203),分別是0、90、180、270、360,時間間隔供8秒。同時,對方向光進行跟蹤設(shè)置,保證旋轉(zhuǎn)時方向光能夠明亮照射到3D對象。圖2-206中的“旋轉(zhuǎn)”按鈕(名為xuanzhuan)的事件代碼就是啟動此故事板。WPF中設(shè)計故事板時會自動生成事件觸發(fā)器,自動啟動故事板,本例將觸發(fā)器全部刪除,用代碼啟動后停止。
5. 開瓶器部件拆卸和裝配故事板設(shè)計
開瓶器部件拆卸共設(shè)計了5個故事板,StoryBoard1到StoryBoard5(程序中的名稱分別是mystoryboard1到mystoryboard5),分別順序用于設(shè)計拆卸“左翼螺釘”、“右翼螺釘”、“左翼”、“右翼”、“開瓶器把手”的動畫。
開瓶器部件裝配也設(shè)計了5個故事板,StoryBoard6到StoryBoard10(程序中的名稱分別是mystoryboard6到mystoryboard10),分別順序用于設(shè)計裝配“開瓶器把手”、
“右翼” 、“左翼”、“右翼螺釘”和“左翼螺釘”的動畫。
拆卸動畫和裝配過程的動畫運動過程是相反的,拆卸動畫的終點參數(shù)應(yīng)該是裝配動畫的起點參數(shù),裝配動畫的終點參數(shù)是拆卸動畫的起點參數(shù),動畫時間間隔可以一樣,運動路徑可以有差異,但起點和終點參數(shù)必須對應(yīng),否則部件就不能還原到原來位置了。動畫設(shè)計過程是雷同的,圖2-209左圖是開瓶器所有可拆卸部件全部拆卸后在屏幕中的放置位置布局。

圖2-209
開瓶器部件拆卸后放置在屏幕的位置布局和“開瓶器把手”拆卸故事板設(shè)計
下面以“開瓶器把手”為例,說明其拆卸動畫和裝配動畫的設(shè)計。
“開瓶器把手”的拆卸動畫故事板是StoryBoard5(程序中名為mystoryboard5),設(shè)計圖如圖2-209右下圖。拆卸故事板有10個關(guān)鍵幀。“開瓶器把手”的裝配動畫故事板是StoryBoard6(程序中名為mystoryboard6),設(shè)計圖如圖2-209右上圖。裝配故事板同樣有10個關(guān)鍵幀。對應(yīng)的變換參數(shù)如表2-2。
從表2-2的參數(shù)中可以看出拆卸動畫的終點參數(shù)是裝配動畫的起點參數(shù),裝配動畫的終點參數(shù)是拆卸動畫的起點參數(shù),中間的參數(shù)有差異僅僅反映中間運動過程有異,這并不重要。
其他故事版的設(shè)計雷同,不再列出。
6. 程序設(shè)計
程序設(shè)計有下面幾點要說明:
第一,圖2-206中有4個按鈕,其中有1個“復(fù)位”按鈕,恢復(fù)三維對象的原來狀態(tài),使用刪除多于變換的方法?!靶D(zhuǎn)”按鈕啟動的是StoryBoard0故事板?!白詣硬鹦丁卑粹o單擊后將會依次啟動故事板StoryBoard1到StoryBoard5,“自動裝配”按鈕單擊后將會依次啟動故事板StoryBoard6到StoryBoard10。
第二,故事板的控制沒有使用觸發(fā)器,自動生成的所有觸發(fā)器均被刪除,故事板的控制采用前面介紹過的利用故事板資源設(shè)置代碼控制故事板。
第三,故事板的依次啟動指前一個故事板完成后才能啟動后一個故事版,這樣在程序上需要設(shè)置故事板的Completed事件。
表2-2 StoryBoard5和StoryBoard6關(guān)鍵幀參數(shù)設(shè)置
時間
|
拆卸動畫StoryBoard5
|
裝配動畫StoryBoard6
|
位移變換參數(shù)
坐標X、Y、Z
|
旋轉(zhuǎn)變換參數(shù)
角度X、Y、Z
|
位移變換參數(shù)
坐標X、Y、Z
|
旋轉(zhuǎn)變換參數(shù)
角度X、Y、Z
|
0
|
0,0,0
|
0,0,0
|
0,110,0
|
0,0,0
|
1
|
0,10,0
|
0,90,0
|
0,90,0
|
0,0,0
|
2
|
0,20,0
|
0,180,0
|
0,70,0
|
0,0,0
|
3
|
0,30,0
|
0,270,0
|
0,60,0
|
0,0,0
|
4
|
0,40,0
|
0,360,0
|
0,50,0
|
0,0,0
|
5
|
0,50,0
|
0,90,0
|
0,40,0
|
0,0,0
|
6
|
0,60,0
|
0,180,0
|
0,30,0
|
0,-90,0
|
7
|
0,70,0
|
0,270,0
|
0,20,0
|
0,-180,0
|
8
|
0,90,0
|
0,360,0
|
0,10,0
|
0,-270,0
|
9
|
0,110,0
|
0,360,0
|
0,0,0
|
0,-360,0
|
下面是程序代碼,有相關(guān)解釋,不再贅述。
public partial class Window3 :
Window
{
//旋轉(zhuǎn)故事板
Storyboard mystoryboard0=new Storyboard();
//拆卸故事板
Storyboard mystoryboard1=new Storyboard();
Storyboard mystoryboard2=new Storyboard();
Storyboard mystoryboard3=new Storyboard();
Storyboard mystoryboard4=new Storyboard();
Storyboard mystoryboard5=new Storyboard();
//裝配故事板
Storyboard mystoryboard6=new Storyboard();
Storyboard mystoryboard7=new Storyboard();
Storyboard mystoryboard8=new Storyboard();
Storyboard mystoryboard9=new Storyboard();
Storyboard mystoryboard10=new Storyboard();
//定義鼠標跟隨對象,FollowMouse3D是自定義類
FollowMouse3D fm3d=new FollowMouse3D();
Point mouseLastPosition;
//定義變量,記憶相機位置坐標
double cameraX,cameraY,cameraZ;
//設(shè)置三維變換組變量
Transform3DGroup GroupTF3D;
//記憶三維變換組中的子變換數(shù)
int transforms;
public Window3()
{
this.InitializeComponent();
mystoryboard0=(Storyboard)this.FindResource("Storyboard0");
mystoryboard1=(Storyboard)this.FindResource("Storyboard1");
mystoryboard2=(Storyboard)this.FindResource("Storyboard2");
mystoryboard3=(Storyboard)this.FindResource("Storyboard3");
mystoryboard4=(Storyboard)this.FindResource("Storyboard4");
mystoryboard5=(Storyboard)this.FindResource("Storyboard5");
mystoryboard6=(Storyboard)this.FindResource("Storyboard6");
mystoryboard7=(Storyboard)this.FindResource("Storyboard7");
mystoryboard8=(Storyboard)this.FindResource("Storyboard8");
mystoryboard9=(Storyboard)this.FindResource("Storyboard9");
mystoryboard10=(Storyboard)this.FindResource("Storyboard10");
//聲明故事板完成事件
mystoryboard1.Completed+=new
System.EventHandler(mystoryboard1_Completed);
mystoryboard2.Completed+=new
System.EventHandler(mystoryboard2_Completed);
mystoryboard3.Completed+=new
System.EventHandler(mystoryboard3_Completed);
mystoryboard4.Completed+=new
System.EventHandler(mystoryboard4_Completed);
mystoryboard5.Completed+=new
System.EventHandler(mystoryboard5_Completed);
mystoryboard6.Completed+=new
System.EventHandler(mystoryboard6_Completed);
mystoryboard7.Completed+=new
System.EventHandler(mystoryboard7_Completed);
mystoryboard8.Completed+=new
System.EventHandler(mystoryboard8_Completed);
mystoryboard9.Completed+=new
System.EventHandler(mystoryboard9_Completed);
mystoryboard10.Completed+=new
System.EventHandler(mystoryboard10_Completed);
//遠景相機初始位置
cameraX=ppc.Position.X;
cameraY=ppc.Position.Y;
cameraZ=ppc.Position.Z;
//聲明或獲取當(dāng)前World的三維變換組(xaml中)Transform3DGroup
GroupTF3D = World.Transform as Transform3DGroup;
//記錄三維變換組中子變換的總數(shù)
transforms=GroupTF3D.Children.Count;
//故事板屬性設(shè)置
this.mystoryboard0.RepeatBehavior=RepeatBehavior.Forever;
this.mystoryboard0.FillBehavior=FillBehavior.Stop;
this.mystoryboard0.BeginTime=TimeSpan.FromSeconds(2);
this.mystoryboard1.BeginTime=TimeSpan.FromSeconds(2);
this.mystoryboard6.BeginTime=TimeSpan.FromSeconds(2);
this.mystoryboard0.Begin();
}
//復(fù)位按鈕,調(diào)用自定義方法(復(fù)位操作)
private void reset_Click(object
sender, System.Windows.RoutedEventArgs e)
{
Reset();
}
//自定義方法,復(fù)位操作
private void Reset(){
this.mystoryboard0.Stop();
//恢復(fù)相機初始位置
ppc.Position = new Point3D(cameraX, cameraY,cameraZ);
int j=GroupTF3D.Children.Count;
//保留原來的變換數(shù),其余刪除
if (j>transforms){
for (int k=j-1;k>transforms-1;){
GroupTF3D.Children.RemoveAt(k);
k=GroupTF3D.Children.Count-1;
}
}
}
//旋轉(zhuǎn)按鈕事件
private void xuanzhuan_Click(object sender,
System.Windows.RoutedEventArgs e)
{
this.mystoryboard0.Begin();
}
//自動拆卸
private void button6_Click(object sender,
System.Windows.RoutedEventArgs e)
{
Reset();
this.mystoryboard1.Begin();//左翼螺釘拆卸
}
private void mystoryboard1_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard2.Begin();//右翼螺釘拆卸
}
private void mystoryboard2_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard3.Begin();////左翼拆卸
}
private void mystoryboard3_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard4.Begin();//右翼拆卸
}
private void mystoryboard4_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard5.Begin();//開瓶器把手拆卸
}
private void mystoryboard5_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard0.Begin();//拆卸完成啟動旋轉(zhuǎn)故事板
}
//自動裝配
private void button7_Click(object sender,
System.Windows.RoutedEventArgs e)
{
Reset();
this.mystoryboard6.Begin();//開瓶器把手裝配
}
private void mystoryboard6_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard7.Begin();//右翼裝配
}
private void mystoryboard7_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard8.Begin();//左翼裝配
}
private void mystoryboard8_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard9.Begin();//右翼螺釘裝配
}
private void mystoryboard9_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard10.Begin();//左翼螺釘裝配
}
private void mystoryboard10_Completed(object sender,
System.EventArgs e)
{
this.mystoryboard0.Begin();//裝配完成啟動故事板
}
}