WPF中对三维模型的控制

原文:WPF中对三维模型的控制

(以下选自南开大学出版社出版的《WPF和Silverlight教程》)

3Dmax中的建模模型可以导出为obj文件格式,将此文件导入WPF项目中,由WPF完成对三维造型的贴图和控制设计。本例在3Dmax中设计了1个双翼开瓶器模型,将“开瓶器.obj”和贴图材质文件都添加到项目中(“素材”文件夹)。图2-206 的左侧是“开瓶器.obj”文件拖入到【设计面板】后,在【对象和时间线】面板中看到的结构,右侧是贴图后的开瓶器模型,中间是本例完成的对开瓶器部件进行拆卸和装配的控制按钮。下面说明设计过程。

1.obj文件导入后的对象

图2-206左侧看到的是obj文件导入后的对象结构,ViewPort3D(命名为viewport3)是三维对象的容器,其中包含相机元素Camera(ModelVisual3D),默认相机是透视相机PerspectiveCamera(已经命名为ppc);World元素(ModelVisual3D)中包含了环境光子元素AmbientLightContainer(ModelVisual3D)、方向光子元素DirectionalLightContainer(ModelVisual3D)和三维造型子元素RootGeometryContainer(ModelVisual3D),后者又包含了qua02(3Dmax中的原名)等6个ModelVisual3D子元素,每个子元素包含造型元素材质设置DefaultMaterial(GeometryModel3D)。

图2-206
开瓶器结构、外观和控制按钮

表2-1三维造型元素名称(3Dmax中的原名)和开瓶器部件名称对照


三维造型元素名


开瓶器部件名


三维造型元素名


开瓶器部件名


qua01


左翼


Object


开瓶器座


qua02


右翼


Object03


左翼螺钉


pemo


开瓶器把手


Object04


右翼螺钉

2. 三维造型元素初始位置

obj文件拖入Window3.xaml【设计面板】后,尽量发大到和设计窗口界面一样大小,这时的大小不一定合适,可以调节的照相机初始位置,如图2-207左图。其中参数是调整后的参数,比如Position,在obj文件刚导入时,X、Y和Z不一定是目前的数值,改变Z参数的数值可以调节三维造型在屏幕中的大小。Direction参数中的X、Y调节到0,相机面向Z坐标轴的负方向。本例中Far
Clipping Plane的参数调节的比较大,当3D对象缩小的很小时还能完整看到造型全貌。

图2-207
照相机初始位置参数设置和World初始变换设置

另外,“World”元素做了位移变换,见图2-207右图,Z坐标使造型大小改变,Y坐标产生上下位移。所有三维造型元素的其他变换参数默认是0,知道这些参数的初始值对后面的故事板设计很重要。

3. 贴图和光线设置

贴图需要的材质图片“金属7.jpg”和“外壳.jpg”已经添加到项目的“素材”文件夹中,将这些图片拖入【设计面板】后生成画刷资源,保存到ResourceDictionary1.xaml资源文件中,用于三维造型元素的材质贴图。“Object”元素(“开瓶器座”)的“DefaultMaterial”贴图使用了“外壳.jpg”生成的画刷资源,“螺钉”没有贴图,采用“白色”材质,其余采用“金属7.jpg”
生成的画刷资源,贴图过程略。

环境光“AmbientLight”采用定向光,设置为白色,方向光DirectionalLight也采用定向光,设置为白色,调整初始角度可以明亮照射3D对象。

4. 开瓶器旋转故事板设计

示例中设计了1个故事板StoryBoard0(程序中的命名为mystoryboard0),用于实现整个造型的三维空间旋转,如图2-208所示。

图2-208 电动机三维空间旋转故事板设计

故事板StoryBoard0针对三维元素“World”和方向光DirectionalLight设置了动画,故事板中有5个关键帧,“World”围绕Y轴进行旋转变换(参考图2-203),分别是0、90、180、270、360,时间间隔供8秒。同时,对方向光进行跟踪设置,保证旋转时方向光能够明亮照射到3D对象。图2-206中的“旋转”按钮(名为xuanzhuan)的事件代码就是启动此故事板。WPF中设计故事板时会自动生成事件触发器,自动启动故事板,本例将触发器全部删除,用代码启动后停止。

5. 开瓶器部件拆卸和装配故事板设计

开瓶器部件拆卸共设计了5个故事板,StoryBoard1到StoryBoard5(程序中的名称分别是mystoryboard1到mystoryboard5),分别顺序用于设计拆卸“左翼螺钉”、“右翼螺钉”、“左翼”、“右翼”、“开瓶器把手”的动画。

开瓶器部件装配也设计了5个故事板,StoryBoard6到StoryBoard10(程序中的名称分别是mystoryboard6到mystoryboard10),分别顺序用于设计装配“开瓶器把手”、
“右翼” 、“左翼”、“右翼螺钉”和“左翼螺钉”的动画。

拆卸动画和装配过程的动画运动过程是相反的,拆卸动画的终点参数应该是装配动画的起点参数,装配动画的终点参数是拆卸动画的起点参数,动画时间间隔可以一样,运动路径可以有差异,但起点和终点参数必须对应,否则部件就不能还原到原来位置了。动画设计过程是雷同的,图2-209左图是开瓶器所有可拆卸部件全部拆卸后在屏幕中的放置位置布局。

图2-209
开瓶器部件拆卸后放置在屏幕的位置布局和“开瓶器把手”拆卸故事板设计

下面以“开瓶器把手”为例,说明其拆卸动画和装配动画的设计。

“开瓶器把手”的拆卸动画故事板是StoryBoard5(程序中名为mystoryboard5),设计图如图2-209右下图。拆卸故事板有10个关键帧。“开瓶器把手”的装配动画故事板是StoryBoard6(程序中名为mystoryboard6),设计图如图2-209右上图。装配故事板同样有10个关键帧。对应的变换参数如表2-2。

从表2-2的参数中可以看出拆卸动画的终点参数是装配动画的起点参数,装配动画的终点参数是拆卸动画的起点参数,中间的参数有差异仅仅反映中间运动过程有异,这并不重要。

其他故事版的设计雷同,不再列出。

6. 程序设计

程序设计有下面几点要说明:

第一,图2-206中有4个按钮,其中有1个“复位”按钮,恢复三维对象的原来状态,使用删除多于变换的方法。“旋转”按钮启动的是StoryBoard0故事板。“自动拆卸”按钮单击后将会依次启动故事板StoryBoard1到StoryBoard5,“自动装配”按钮单击后将会依次启动故事板StoryBoard6到StoryBoard10。

第二,故事板的控制没有使用触发器,自动生成的所有触发器均被删除,故事板的控制采用前面介绍过的利用故事板资源设置代码控制故事板。

第三,故事板的依次启动指前一个故事板完成后才能启动后一个故事版,这样在程序上需要设置故事板的Completed事件。

表2-2 StoryBoard5和StoryBoard6关键帧参数设置


时间


拆卸动画StoryBoard5


装配动画StoryBoard6


位移变换参数

坐标X、Y、Z


旋转变换参数

角度X、Y、Z


位移变换参数

坐标X、Y、Z


旋转变换参数

角度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

下面是程序代码,有相关解释,不再赘述。

public partial class Window3 :
Window

{

//旋转故事板

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;

//设置三维变换组变量

Transform3DGroup GroupTF3D;

//记忆三维变换组中的子变换数

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;

//声明或获取当前World的三维变换组(xaml中)Transform3DGroup

GroupTF3D = World.Transform as Transform3DGroup;

//记录三维变换组中子变换的总数

transforms=GroupTF3D.Children.Count;

//故事板属性设置

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();

}

//复位按钮,调用自定义方法(复位操作)

private void reset_Click(object
sender, System.Windows.RoutedEventArgs e)

{

Reset();

}

//自定义方法,复位操作

private void Reset(){

this.mystoryboard0.Stop();

//恢复相机初始位置

ppc.Position = new Point3D(cameraX, cameraY,cameraZ);

int j=GroupTF3D.Children.Count;

//保留原来的变换数,其余删除

if (j>transforms){

for (int k=j-1;k>transforms-1;){

GroupTF3D.Children.RemoveAt(k);

k=GroupTF3D.Children.Count-1;

}

}

}

//旋转按钮事件

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();//拆卸完成启动旋转故事板

}

//自动装配

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();//装配完成启动故事板

}

}

时间: 2024-07-30 03:23:50

WPF中对三维模型的控制的相关文章

在WPF中使用AForge.net控制摄像头拍照

原文:在WPF中使用AForge.net控制摄像头拍照 利用AForge.net控制摄像头拍照最方便的方法就是利用PictureBox显示摄像头画面,但在WPF中不能直接使用PictureBox.必须通过<WindowsFormsHost></WindowsFormsHost>来提供交换功能.其解决方法如下: 1.按照常规方法新建一个WPF应用程序: 2.添加引用 WindowsFormsIntegration  (与WinForm交互的支持) System.Windows.For

浏览器扩展系列————在WPF中定制WebBrowser快捷菜单

原文:浏览器扩展系列----在WPF中定制WebBrowser快捷菜单 关于如何定制菜单可以参考codeproject上的这篇文章:http://www.codeproject.com/KB/books/0764549146_8.aspx?fid=13574&df=90&mpp=25&noise=3&sort=Position&view=Quick&fr=26#xx0xx 本文主要讲述如何在这篇文章中的ShowContextMenu方法中弹出自己的Conte

WPF快速指导10:WPF中的事件及冒泡事件和隧道事件(预览事件)的区别

本文摘要: 1:什么是路由事件: 2:中断事件路由: 3:自定义路由事件: 4:为什么需要自定义路由事件: 5:什么是冒泡事件和预览事件(隧道事件): 1:什么是路由事件 WPF中的事件为路由事件,所谓路由事件,MSDN定义如下: 功能定义:路由事件是一种可以针对元素树中的多个侦听器(而不是仅针对引发该事件的对象)调用处理程序的事件. 实现定义:路由事件是一个 CLR 事件,可以由 RoutedEvent 类的实例提供支持并由 Windows Presentation Foundation (W

WPF中的导航框架(一)——概述

有的时候,我们需要一个支持页面跳转的UI,例如文件浏览器,开始向导等.对于这样的界面,简单的可以使用ContentControl + ContentTemplateSelector的方式来实现,但是有的时候我们会需要一些更加高级的跳转功能,如前进,回退等.这个时候,用这个方式就稍微有点力不从心了,此时,我们可以使用WPF的导航框架帮助我们快速实现这一功能. WPF 的Page框架主要包括两个部分,容器和页面, 下面就以一个简单的例子来介绍WPF的Page框架,首先我们创建第一个页面: <Page

WPF中的瀑布流布局(TilePanel)控件

最近在用wpf做一个metro风格的程序,需要用到win8风格的布局容器,只能自己写一个了.效果如下 用法 : <local:TilePanel                          TileMargin="1"                         Orientation="Horizontal"                         TileCount="4" > //todo 放置内容 //loc

WPF中的导航框架

有的时候,我们需要一个支持页面跳转的UI,例如文件浏览器,开始向导等.对于这样的界面,简单的可以使用ContentControl + ContentTemplateSelector的方式来实现,但是有的时候我们会需要一些更加高级的跳转功能,如前进,回退等.这个时候,用这个方式就稍微有点力不从心了,此时,我们可以使用WPF的导航框架帮助我们快速实现这一功能. WPF 的Page框架主要包括两个部分,容器和页面, 下面就以一个简单的例子来介绍WPF的Page框架,首先我们创建第一个页面: <Page

ckrule规则编辑器在wpf中的使用

当前,ckrule的IDE和业务管理系统都是由winform开发的,规则编辑器也只提供了winform的版本,所以很多的朋友都提出意见,要有wpf的版本.wpf的界面设置和管理都更加的方便. 事实上可以在wpf中使用ckrule规则编辑器的,使用的方法如下:     1,在wpf项目中引入windowsform集成的相关dll. 包含2个dll,分别是System.Windows.Forms.dll和WindowsFormsIntegration.dll     2,引入WindowsForms

WPF中的事件列表 .

以下是WPF中的常见事件汇总表(按字母排序),翻译不见得准确,但希望对你有用. 事件 描述 Annotation.AnchorChanged 新增.移除或修改 Anchor 元素时发生. Annotation.AuthorChanged 新增.移除或修改 Author 元素时发生. Annotation.CargoChanged 新增.移除或修改 Cargo 元素时发生. AnnotationStore.AnchorChanged 存放区中任何注释上的 Anchor 元素变化时发生. Annot

【转】WPF中实现自定义虚拟容器(实现VirtualizingPanel)

在WPF应用程序开发过程中,大数据量的数据展现通常都要考虑性能问题.有下面一种常见的情况:原始数据源数据量很大,但是某一时刻数据容器中的可见元素个数是有限的,剩余大多数元素都处于不可见状态,如果一次性将所有的数据元素都渲染出来则会非常的消耗性能.因而可以考虑只渲染当前可视区域内的元素,当可视区域内的元素需要发生改变时,再渲染即将展现的元素,最后将不再需要展现的元素清除掉,这样可以大大提高性能.在WPF中System.Windows.Controls命名空间下的VirtualizingStackP