原文: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中进行碰撞检测

    前文我简要的介绍了在WPF中,如何控制摄像头移动,已达到动画的效果.也带来了一个新的问题:摄像头移动的时候,毫无阻拦,这就是所谓的"穿墙模式".有没有什么办法解决这个问题呢?有,就 ...

  2. WPF中的动画——(二)From/To/By 动画

    原文:WPF中的动画--(二)From/To/By 动画 我们所实现的的动画中,很大一部分是让一个属性在起始值和结束值之间变化,例如,我在前文中实现的改变宽度的动画: var widthAnimati ...

  3. WPF中的动画——(六)演示图板

    前面所介绍的都是单一的动画,它只能修改单一属性.有的时候,我们需要将一组动画一起进行,对于一个按钮,我们可能有如下需求: 选择该按钮时,该按钮增大并更改颜色. 单击该按钮时,该按钮缩小并恢复其原始大小 ...

  4. 了解 WPF 中的路由事件和命令

    目录 路由事件概述 WPF 元素树 事件路由 路由事件和组合 附加事件 路由命令概述 操作中的路由命令 命令路由 定义命令 命令插入 路由命令的局限 避免命令出错 超越路由命令 路由处理程序示例 要想 ...

  5. 在WPF中实现平滑滚动

    原文:在WPF中实现平滑滚动 WPF实现滚动条还是比较方便的,只要在控件外围加上ScrollViewer即可,但美中不足的是:滚动的时候没有动画效果.在滚动的时候添加过渡动画能给我们的软件增色不少,例 ...

  6. WPF中的动画——(二)From/To/By 动画(二)

    WPF中的动画--(二)From/To/By 动画 我们所实现的的动画中,很大一部分是让一个属性在起始值和结束值之间变化,例如,我在前文中实现的改变宽度的动画: var widthAnimation ...

  7. 深入WPF中的图像画刷(ImageBrush)之1——ImageBrush使用举例

    深入WPF中的图像画刷(ImageBrush)之1--ImageBrush使用举例 2010年06月11日 星期五 15:20 昨天我在<简述WPF中的画刷(Brush) >中简要介绍了W ...

  8. Windows Presentation Foundation (WPF)中的命令(Commands)简述

    Windows Presentation Foundation (WPF)中的命令(Commands)简述 原文:Windows Presentation Foundation (WPF)中的命令(C ...

  9. Binding在WPF中的使用

    闲来无事,不想打DOTA,在这里小小研究下wpf中关于Binding的东西. 咯咯 在我们印象中,Binding的意思是"绑定",这个"绑"大概取自于Bind这 ...

最新文章

  1. SQL Server Extended Events 进阶 3:使用Extended Events UI
  2. 浏览器同源策略及Cookie的作用域
  3. c语言解逻辑问题的一般步骤,C语言面试题---逻辑短路问题
  4. 转 无障碍阅读 role aria-*
  5. 数据库连接oracle 10g rman 备份与恢复 之一
  6. python 查找指定文件_python实现在目录中查找指定文件的方法
  7. 数学里最令人着迷的公式之一--欧拉公式!
  8. 再不解决延迟不当,小心你的内存被打爆
  9. WinCE学习系列(1)——在VS2008的环境下安装WinCE 5.0仿真模拟器
  10. 电脑蓝牙在哪里打开_华为手机与华为Matebook笔记本如何通过蓝牙传输文件
  11. php upload 上传类,发布一个PHP的文件上传类——Uploader
  12. 模块化开发RequireJS之shim配置
  13. 记渣渣烟和专车司机的一次聊天
  14. Mobile开发(绘制背景图片)
  15. 小猿圈python学习-Selenium爬虫之使用代理ip的方法
  16. 安卓图像处理(四)保存以及删除图片
  17. Kotlin 编码规约
  18. 小鱼的数字游戏递归解
  19. [Re]2022DASCTF Apr X FATE 防疫挑战赛
  20. js面试题Foo.getName()的故事

热门文章

  1. MYSQL 集群的数据节点错误信息归档
  2. ISA Server 2006 的内部客户端概念
  3. 在maven项目中使用Junit进行单元测试(一)
  4. android 内部类的优化
  5. 极客广州——EOS Asia郭达峰担任SegmentFault思否黑客马拉松技术顾问
  6. 【Git】Git 本地的撤销修改和删除操作
  7. DB time抖动的原因分析
  8. PHP memcache实现消息队列实例
  9. MongoDB 日志切换(Rotate Log Files)实战
  10. Spring MVC 解决日期类型动态绑定问题