原文: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中对三维模型的控制的更多相关文章

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

    原文:在WPF中使用AForge.net控制摄像头拍照 利用AForge.net控制摄像头拍照最方便的方法就是利用PictureBox显示摄像头画面,但在WPF中不能直接使用PictureBox.必须 ...

  2. WPF wpf中按钮操作权限控制

    权限控制我们有很多种方式可以实现. 这次项目中做个简单的权限控制,我们在所有按钮触发前判断,有权限则可执行. 我们自定义一个命令类. public class DelegateCommand : IC ...

  3. WPF中使用RenderTransformOrigin来控制动画的起点

    Render :渲染:Transform:动画:Origin:起点 RenderTransformOrigin:渲染动画的起点 取值为一个坐标的形式  取值范围: 0,0 到 1,1 0,0:表示左上 ...

  4. 用游戏杆控制WPF中三维模型

    原文:用游戏杆控制WPF中三维模型 用游戏杆控制WPF中三维模型   今天心情比较好,不写WF的文章了,换个主题.写一个我最最最擅长的内容.   例子下载: http://files.cnblogs. ...

  5. MVVM模式解析和在WPF中的实现(五)View和ViewModel的通信

    MVVM模式解析和在WPF中的实现(五) View和ViewModel的通信 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 M ...

  6. MVVM模式和在WPF中的实现(一)MVVM模式简介

    MVVM模式解析和在WPF中的实现(一) MVVM模式简介 系列目录: MVVM模式解析和在WPF中的实现(一)MVVM模式简介 MVVM模式解析和在WPF中的实现(二)数据绑定 MVVM模式解析和在 ...

  7. WPF中的动画——(三)时间线(TimeLine)

    WPF中的动画——(三)时间线(TimeLine) 时间线(TimeLine)表示时间段. 它提供的属性可以让控制该时间段的长度.开始时间.重复次数.该时间段内时间进度的快慢等等.在WPF中内置了如下 ...

  8. WPF中的数据绑定!!!

    引用自:https://msdn.microsoft.com/zh-cn/magazine/cc163299.aspx  数据点: WPF 中的数据绑定 数据点 WPF 中的数据绑定 John Pap ...

  9. WPF中图形表示语法详解(Path之Data属性语法)ZZ

    大可山 [MSN:a3news(AT)hotmail.com] http://www.zpxp.com 萝卜鼠在线图形图像处理 ------------------------------------ ...

随机推荐

  1. 怎样用Java编写一段代码引发内存泄露

    通过下面步骤能够非常easy产生内存泄露(程序代码不能訪问到某些对象,可是它们仍然保存在内存中): 应用程序创建一个长时间执行的线程(或者使用线程池,会更快地发生内存泄露). 线程通过某个类载入器(能 ...

  2. CentOS上解压ZIP乱码的解决办法

    今天在学校做实验需要解压缩一些范例程序,我准备在我的电脑上把这个实验做完,所以就把文件copy到我的CentOS里面去了. 但是万万没想到``````解压缩的时候悲剧了,用unzip解压出来一大堆乱码 ...

  3. 从零开始,使用python快速开发web站点(2)

    书接上文.http://blog.csdn.net/i7788/article/details/10306595 首先是数据库的搭建,这里的django的数据模型搭建十分easy. no sql.ju ...

  4. 《sql---教学反馈系统-阶段项目2》

    /* a) 创建数据库 使用T-SQL创建数据库feedback,要求:①一个主要文件(存放在第一个硬盘分区C:\project文件夹下),初始大小为10M,最大为200M,文件自动增长率为15% ② ...

  5. hdu5179(数位dp)

    传送门:beautiful number 题意:令 A=∑ni=1ai?10n?i(1≤ai≤9)(n为A的位数).若A为“漂亮的数”当且仅当对于任意1≤i<n满足a[i]≥a[i+1]且对于任 ...

  6. Linux 系统文件夹结构

    登录后,在当前的命令窗口中输入命令: <span style="font-size:14px;">ls /</span> 你的终端上会出现例如以下的代码: ...

  7. 移动M站建设

    电商总结(五)移动M站建设   最近在一直在搞M站,也就是移动web站点.由于是第一次,也遇到了很多问题,所以把最近了解到的东西总结总结.聊一聊什么是移动M站,它有啥作用和优势. 也有人会问,M站和A ...

  8. hdu1698(线段树)

    题目连接:http://acm.hdu.edu.cn/showproblem.php?pid=1698 线段树功能:update:成段替换 (由于只query一次总区间,所以可以直接输出1结点的信息) ...

  9. JsonCpp Documentation

    JsonCpp - JSON data format manipulation library JsonCpp Documentation 0.6.0-rc2 Introduction JSON (J ...

  10. gcc的bug? c++模板类中友元函数的訪问权限问题

    原文地址:http://stackoverflow.com/q/23171337/3309790 在c++中,模板类中能够直接定义一个友元函数.该函数拥有訪问该模板类非public成员的权限. 比方: ...