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();//装配完成启动故事板
}
}
WPF中对三维模型的控制的更多相关文章
- 在WPF中使用AForge.net控制摄像头拍照
原文:在WPF中使用AForge.net控制摄像头拍照 利用AForge.net控制摄像头拍照最方便的方法就是利用PictureBox显示摄像头画面,但在WPF中不能直接使用PictureBox.必须 ...
- WPF wpf中按钮操作权限控制
权限控制我们有很多种方式可以实现. 这次项目中做个简单的权限控制,我们在所有按钮触发前判断,有权限则可执行. 我们自定义一个命令类. public class DelegateCommand : IC ...
- WPF中使用RenderTransformOrigin来控制动画的起点
Render :渲染:Transform:动画:Origin:起点 RenderTransformOrigin:渲染动画的起点 取值为一个坐标的形式 取值范围: 0,0 到 1,1 0,0:表示左上 ...
- 用游戏杆控制WPF中三维模型
原文:用游戏杆控制WPF中三维模型 用游戏杆控制WPF中三维模型 今天心情比较好,不写WF的文章了,换个主题.写一个我最最最擅长的内容. 例子下载: http://files.cnblogs. ...
- MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信
MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...
- MVVM模式和在WPF中的实现(一)MVVM模式简介
MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...
- WPF中的动画——(三)时间线(TimeLine)
WPF中的动画——(三)时间线(TimeLine) 时间线(TimeLine)表示时间段. 它提供的属性可以让控制该时间段的长度.开始时间.重复次数.该时间段内时间进度的快慢等等.在WPF中内置了如下 ...
- WPF中的数据绑定!!!
引用自:https://msdn.microsoft.com/zh-cn/magazine/cc163299.aspx 数据点: WPF 中的数据绑定 数据点 WPF 中的数据绑定 John Pap ...
- WPF中图形表示语法详解(Path之Data属性语法)ZZ
大可山 [MSN:a3news(AT)hotmail.com] http://www.zpxp.com 萝卜鼠在线图形图像处理 ------------------------------------ ...
随机推荐
- DTD学习笔记
1. DTD基本介绍 xml文件分为两种类型,一个是在好形式,这是well-formed,还有一个合法有效,这是valid. XML文件遵循-called"好形式"各种语法规则要 ...
- IBinder在进程之间传递一个对象的形式(一)
主张 什么时候service通常被称为远程时的,用到aidl来定一个接口供service和client来使用,这个事实上就是使用Binder机制的IPC通信.当client bind service成 ...
- ssh登录过程详细介绍
服务器端和客户端就取得了相同的会话密钥和会话 ID .对于后续传输的数据,两端都会使用会话密钥进行加密和解密,保证了数据传送的安全. http://blog.csdn.net/lhq9220/arti ...
- ThinkPHP分页使用例子(二十一)
原文:ThinkPHP分页使用例子(二十一) ThinkPHP分页使用 PHP代码: public function fenye(){ $User = M('Leyangjun'); // 实例化Us ...
- java之jvm学习笔记五(实践写自己的类装载器)
java之jvm学习笔记五(实践写自己的类装载器) 课程源码:http://download.csdn.net/detail/yfqnihao/4866501 前面第三和第四节我们一直在强调一句话,类 ...
- STM32M CUBE实现printf打印调试信息以及实现单字节接收
在写单片机程序时我们一般喜欢使用printf来通过串口打印调试信息,但这个函数是不能够直接使用的.必须做点对库函数的修改. 具体project下载地址: http://download.csdn.ne ...
- Mod in math
An Introduction to Modular Math When we divide two integers we will have an equation that looks like ...
- hbase基本概念和hbase shell经常使用命令使用方法
HBase是一个分布式的.面向列的开源数据库,源于google的一篇论文<bigtable:一个结构化数据的分布式存储系统>.HBase是Google Bigtable的开源实现,它利用H ...
- Wix学习整理(3)——关于Windows Installer和MSI
原文:Wix学习整理(3)--关于Windows Installer和MSI 关于Windows Installer Windows Installer是微软Windows操作系统自带的一个软件安装和 ...
- dell服务器从硬盘导入阵列信息
前几天去南京客户那里更新新的业务系统,客户要求将服务器上的旧的硬盘拆下来,换上新的硬盘,重新做raid,客户自己要插入旧的硬盘读取旧数据,昨天做了几个实验,两台Dell R710服务器各4块硬盘,一台 ...