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 萝卜鼠在线图形图像处理 ------------------------------------ ...
随机推荐
- Spire.XLS for .NET 測评
有一位朋友推荐了我 Spire.Office (官网:http://www.e-iceblue.com/) -- 基于.NET的办公软件库,说不错. 究竟怎样呢?仅仅有亲測一下才干知道了. ...
- win7+vs2008+opencv
1.下载安装VS2008,然后直接下载opencv的windows的安装版, 2.把opencv解压出来,我的路径为:D:\Program\opencv 3.配置PATH:电脑--属性--高级系统设置 ...
- java之jvm学习笔记二(类装载器的体系结构)
java的class只在需要的时候才内转载入内存,并由java虚拟机的执行引擎来执行,而执行引擎从总的来说主要的执行方式分为四种, 第一种,一次性解释代码,也就是当字节码转载到内存后,每次需要都会重新 ...
- 理解Spring的Bean工厂
一提到工厂,我们先来回顾前面学习过的工厂方法和抽象工厂模式: 工厂方法:针对产品维度,能够产生新的产品,也能够产生新的产品工厂,既能够扩展产品维度.可是假设我们想在普通工厂上生产产品系列,就会特别麻烦 ...
- hdu 3405 world islands
求删点后最小的生成树,n<50....数据好弱,直接暴力枚举就行...删点的时候直接g[i][j]=INF就行了. #include<iostream> #include<al ...
- hdu 1392(凸包)
传送门:Surround the Trees 题意:求凸包的周长. 分析:凸包模板题,先按极角排好序后,然后根据叉积正负确定凸包. #include <stdio.h> #include ...
- SICP 解题集 — SICP 解题集
SICP 解题集 — SICP 解题集 SICP 解题集¶ 这个文档的目标是成为中文化的.完整的<计算机程序的构造和解释>一书的解题集. 这个解题集的特色是: 对于每道习题,除了习题答案之 ...
- 解决NGINX的WORDPRESS伪静态规则失效的问题
解决NGINX的WORDPRESS伪静态规则失效的问题 前两天搬到了EMSVPS的PR线路上,用上了最新的WDCP2.0管理面板,支持多用户管理(我们几个合租的VPS,最需要这个功能了),感觉很不错, ...
- freemarker自己定义标签(二)
freemarker自己定义标签 1.自己定义标签 通过自己定义标签,写一个反复指定字符串 2.实现源代码 <html> <head> <meta http-equiv= ...
- Leetcode_num13_Climbing Stairs
称号: You are climbing a stair case. It takes n steps to reach to the top. Each time you can either cl ...