使用 Silverlight 3.0 构建高级 3D 动画

如果由于横生波折,您与过去几个月内发布所有 SilverlightTM 方面的信息均失之交臂,我来为您补上这一课:Silverlight 是 Microsoft 出品的一款新的跨浏览器插件,它引进了 Microsoft® .NET Framework 的强大功能,用于履行之前保留给 Flash 或 Java 小程序的职责。Silverlight 有大量非常有用的现成功能。它支持小而精的 .NET Framework 3.5,此版本除了其他功能外,还包括 XML 和可扩展应用程序标记语言 (XAML)、泛型集合、Web 服务和 LINQ。Silverlight 还支持多种与 .NET 兼容的语言,但在这里我将仅使用 C#。 

  我认为掌握一项新技术的最好方法是从中寻找一些乐趣。因此,Silverlight 1.1 alpha 发布后,当我在都柏林的 IMT 会议上看到 Tim Sneath 令人激动的演示时,我就决定打造一个颇具培训色彩的应用程序,展示如何通过折叠一个平面来形成各种 3D 形状(即多面体)。默认情况下,Silverlight 并不支持 3D,因此需构建 DirectX® 算法库的模拟来操作 3D。

 多面体是具有平面的三维对象。此 Silverlight 示例将研究称为柏拉图多面体的正多面体和称为阿基米德多面体的半正多面体。这些多面体的表面均是正多边形(即所有边的长度都相同),如等边三角形或正方形。它们也可以是球体外表面,即没有尖角。正如您可能从这些古希腊名字中猜出来的,这些对象长久以来都具有迷人的人文内涵。若感兴趣,可在 George Hart 的网站上找到有关它们的更多信息,地址为:georgehart.com/virtual-polyhedra/vp.html

  可在图 1 中或在www.picturespice.com/ps/Polyhedra/ClientBin/TestPage.html. 上查看最终应用程序的演示。应用程序的基本功能是:允许通过将鼠标移动到某个形状(多面体)来选中它。然后,窗口右上角会显示有关所做选择的一些信息,并且您还会看到将平板折叠成所选多面体的动画。最后,如果单击“Cycle”(循环)按钮,程序会自动依次循环显示每个形状。

  Figure 1Silverlight Demonstration of Polyhedra(单击该图像获得较大视图)

  使用 XAML

  与许多 Silverlight 应用程序一样,多边形大量使用内容定义语言 XAML,它等价于 HTML,但更加灵活。同样地,尽管可仅使用 HTML 文档对象模型 (DOM) 来创建 HTML 页面,但它并非一个用于生成内容的明智方法,因为编码往往非常耗时,并且所生成页面的初始化速度也非常慢。最好尽可能在页面中使用 HTML 标记,然后在需要灵活性的地方使用 JavaScript 和 DOM 加以扩展。

 在此仅是粗略地体会一下 XAML,其他内容则超出了本文的讨论范围。不过,Charles Petzold 在《Applications= Code+Markup》一书中非常详细地介绍了 XAML。

  以下是我们在学习新语言时都会遇到的“Hello World”示例的 XAML 等价代码:

ContractedBlock.gif ExpandedBlockStart.gif Code
<UserControl x:Class="Polyhedra.Page"
xmlns
="http://schemas.microsoft.com/client/2007"
xmlns:x
="http://schemas.microsoft.com/winfx/2006/xaml"
Width
="400" Height="300">
<Grid x:Name="LayoutRoot" Background="White">
<TextBlock>Hello World</TextBlock>
</Grid>
</UserControl>

 

  根元素是 UserControl。它包含 Grid,而 Grid 又包含具有“Hello World”文本的 TextBlock 元素。

  先简单了解一下 UserControl 属性。简单说来,它为 XAML 定义代码隐藏类的等同项。在解析和加载 XAML 的同时实例化该类。可在构造函数中执行多种实例化,但如果需要更加复杂的操作,通常必须要运行事件处理程序。它是 Silverlight 的一个重要特征。可为多种 XAML 对象附加事件处理程序,并在所选择的与 .NET 兼容的语言中执行,该语言与 JavaScript 不同,对其编译后会开放所有可能性。

 除生成静态页面外,XAML 还具备许多其他功能。其中最强大的功能之一是使用 Storyboard 来动画模拟对 XAML 所指定初始 UI 的更改。(在 Internet Explorer® 5.0 中,向 HTML 添加了类似的功能,称为 HTML+Time。)其中一个示例为动画模拟对某个对象的颜色、可见性或透明度的更改。如果与转换结合使用,Storyboard 还可旋转、缩放或移动对象。

  Storyboard 使用少量甚至无需任何传统代码即可生成所有类型的动画效果。如果与触发器结合使用,从理论上讲,当对象上发生某个事件(如 MouseEnter)时可自动启动各种动画。唉,可是到 2008 年 3 月发布的 Silverlight 2.0 Beta 为止,触发器可处理的事件只有 Loaded。对于其他事件,则需要少量事件处理程序形式的探测代码。我希望这一状况能够很快得到改善。

  Silverlight 中的 Storyboard 具有一个不错的特征,就是它们基于时间而非基于帧。通过使用与不同时间和事件关联的多个独立 storyboard,可简化复杂行为的实现。毕竟,真实世界并非基于帧—帧是电影胶片流传下来的产物。如果使用独立的行为来实现独立的对象,则问题就简单的多啦。

  一些 XAML 示例

  多面体应用程序中间的折叠动画是使用 C# 代码来生成的。而剩余部分基本是在 XAML 中定义的。其中包括循环示例多面体、以多种方式突出显示当前选中多面体的方法以及“Cycle”(循环)按钮(通过一个旋转箭头动画来指示已激活)。

Figure2Cycle(循环)按钮

ContractedBlock.gif ExpandedBlockStart.gif Code
<Canvas x:Name="CycleButton"
Canvas.Top
="250"
MouseLeftButtonDown
="CycleButtonLeftMouseDown" >
<Path x:Name="Cycle" Stroke="#000033"
Fill
="#FFB47C0D" Canvas.Left="10" Canvas.Top="10"
Data
="M 25,35 A 10,10 180 1 0 25,15 L 25,20 L 12.5,10 L 25,0 L 25,
5 A 10,10 180 1 1 25,45 Z"

Width
="50" Height="50">
<Path.RenderTransform>
<RotateTransform x:Name="CycleRotate" Angle="0"
CenterX
="25" CenterY="25"/>
</Path.RenderTransform>
<Path.Resources>
<Storyboard x:Name="CycleLatched">
<DoubleAnimation Storyboard.TargetName="CycleRotate"
Storyboard.TargetProperty
="Angle" From="360" To="0"
Duration
="00:00:02" RepeatBehavior="Forever"/>
</Storyboard>
</Path.Resources>
</Path>
<TextBlock x:Name="CycleCaption" Canvas.Left="65" Canvas.Top="10"
Foreground
="#FFB47C0D" FontSize="30" FontWeight="Bold"
Text
="Cycle" />
<Rectangle Width="200" Height="70" RadiusX="30" RadiusY="30"
Stroke
="#FFB47C0D" StrokeThickness="4" Fill="Transparent"/>
</Canvas>

 

图 3 显示了如何定义其中的一个多面体示例。在该 XAML 的底部,可看到此示例包含用于定义四面体的四个 Polygon。它们位于自身的 Canvas(即 Model0)之内,并且周围有一个名为 Ring0(最初并不可见)的圆环或椭圆。共定义了两个 Storyboard 动画,一个用于 MouseEnter,另一个用于 MouseLeave。MouseEnter 动画会立即显示圆环,增大 Model0 Canvas 的大小,并使其在 0.7 秒时间内变得更加不透明。MouseLeave 动画则完全反转这些更改。

Silverlight: 使用 Silverlight 2.0 构建高级 3D 动画Figure3多面体示例

ContractedBlock.gif ExpandedBlockStart.gif Code
<Canvas x:Name="Canvas0" Width="116.376" Height="116.376"
Canvas.Left
="671.812" Canvas.Top="341.812"
MouseEnter
="TriggerMouseEnter" MouseLeave="TriggerMouseLeave">
<Canvas.Resources>
<Storyboard x:Name="Canvas0MouseEnter">
<DoubleAnimation Duration="00:00:00" Storyboard.TargetName="Ring0"
Storyboard.TargetProperty
="Opacity" From="0.0" To="1.0" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName
="Model0"
Storyboard.TargetProperty
="Opacity" From="0.7" To="1.0" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName
="Model0Scale"
Storyboard.TargetProperty
="ScaleX" From="0.7" To="1.0" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName
="Model0Scale"
Storyboard.TargetProperty
="ScaleY" From="0.7" To="1.0" />
</Storyboard>
<Storyboard x:Name="Canvas0MouseLeave">
<DoubleAnimation Duration="00:00:00" Storyboard.TargetName="Ring0"
Storyboard.TargetProperty
="Opacity" From="1.0" To="0.0" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName
="Model0"
Storyboard.TargetProperty
="Opacity" From="1.0" To="0.7" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName
="Model0Scale"
Storyboard.TargetProperty
="ScaleX" From="1.0" To="0.7" />
<DoubleAnimation Duration="00:00:00.7"
Storyboard.TargetName
="Model0Scale"
Storyboard.TargetProperty
="ScaleY" From="1.0" To="0.7" />
</Storyboard>
</Canvas.Resources>
<Canvas x:Name="Model0" Opacity="0.7"
Width
="116.376" Height="116.376" >
<Canvas.RenderTransform>
<ScaleTransform x:Name="Model0Scale" CenterX="58.188"
CenterY
="58.188" ScaleX="0.7" ScaleY="0.7"/>
</Canvas.RenderTransform>
<Polygon Canvas.ZIndex="-12545653" Fill="#FFCC0000"
Stroke
="#000000" StrokeThickness="1"
Points
="97.566,74.278 43.052,40.971 33.836,99.937 "/>
<Polygon Canvas.ZIndex="-11948057" Fill="#FFCC0000"
Stroke
="#000000" StrokeThickness="1"
Points
="43.052,40.971 97.566,74.278 58.188,37.662 "/>
<Polygon Canvas.ZIndex="-11309683" Fill="#FFCC0000"
Stroke
="#000000" StrokeThickness="1"
Points
="33.836,99.937 43.052,40.971 58.188,37.662 "/>
<Polygon Canvas.ZIndex="-10481036" Fill="#FFCC0000"
Stroke
="#000000" StrokeThickness="1"
Points
="97.566,74.278 33.836,99.937 58.188,37.662 "/>
</Canvas>
<Ellipse x:Name="Ring0" Opacity="0" Stroke="#FFB47C0D"
StrokeThickness
="4" Width="116.376" Height="116.376" />
</Canvas>

 

下面的话有点跑题,您可能想知道是如何计算四面体示例的每个 Polygon 的边角的,并且,应用程序中其他示例的 Polygon 数量明显要多得多。我是懒惰主义的坚决拥护者,只要计算机能更快完成的,我决不会自己来做。因此,我只在生产过程中使用了一个修改后的多面体。该版本依次执行每个示例中心动画的一个帧—该帧具有完全闭合的形状。我将每个结果 Polygon 组放到沿圆周等距分布的一组 Canvas 中,并将它们一起放到一个文件中以备主程序使用。

  要将对象放到圆周上,需以相同的增量 2*PI/NumSamples 增加角度,然后使用坐标(x= Radius*Cos(Angle), y=Radius*Sin(Angle))来指定每个示例的中心点。并且,由于定位对象的 Canvas 使用的是 Left 和 Top 属性,因此需将中心点的位置分别偏移半个宽度和半个高度。

  使用 XAML 的技巧

  正如您所看到的,可使用 XAML 来获得非常丰富的 UI,并且几乎不需要其他代码。即使是大型文件(如 Page.xaml),其初始化速度也快得令人惊讶。在此先与大家分享一些技巧,这是根据我使用 Silverlight 2.0 最新版本(2008 年 3 月 Beta 版)的经验所总结出的。

  我先前提到,当前的触发器仅可针对 Loaded 事件(通过 Begin 方法)自动启动 Storyboard。对于其他事件,需要类似如下的少量事件处理程序形式的探测代码:

public   void  MouseEnterHandler(
object  o, EventArgs e) {
this .MouseEnterStoryBoard.Begin();
}

 

  如果对 Storyboard 采用了命名约定(如在对象名称后紧跟事件名称),则可大大缩减需编码的事件处理程序数量。例如,在多面体中将以下方法用作圆周中所有示例的共享事件处理程序:

ContractedBlock.gif ExpandedBlockStart.gif Code
public void MouseEnterHandler(
object o, EventArgs e) {
this.triggerStoryboard(o,"MouseEnter");
}
private bool triggerStoryboard(
object o, string eventType) {
Canvas el 
= o as Canvas;
string name= el.GetValue(NameProperty) as String;
Storyboard sb 
= el.FindName(name + eventType) as Storyboard;
if (sb != null)
sb.Begin();
return (sb != null);}

 

 

 正如我从 Andy Beaulieu 的“行星般爆炸的 Silverlight 岩石!”示例 (www.andybeaulieu.com/Home/tabid/67/EntryID/73/default.aspx) 中所发现的,要制做基于代码的动画,一个不错的方法是使用设置为一小段时间的一个 Storyboard,并使用一个 Completed 事件处理程序制做一帧动画,然后重新启动 Storyboard:

ContractedBlock.gif ExpandedBlockStart.gif Code
public Page() { // Constructor for "code-behind"
// Required to initialize variables
InitializeComponent();
this.animationTimer.Completed +=
new EventHandler(animationTimer_Completed);
}
void animationTimer_Completed(object sender, EventArgs e) {
[ Do a frame of animation ]
this.animationTimer.Begin();}

 

  Silverlight 2.0 Alpha 九月更新版更改了对 Storyboard 的要求,因此动画现在必须有一个目标,即使并不使用它:

ContractedBlock.gif ExpandedBlockStart.gif Code
<Canvas.Resources>
<Storyboard x:Name="animationTimer">
<DoubleAnimation Duration="00:00:00.01"
Storyboard.TargetName="bogusTimerTarget"
Storyboard.TargetProperty="Width" />
</Storyboard>
</Canvas.Resources>
<Canvas Name="bogusTimerTarget">
</Canvas>

 

 XAML 的好处之一是它省去了 UI 中的大量常规工作,从而使您可集中精力完成创造性的问题域代码。在此示例中,问题域为将平板折叠成 3D 形状,即下一节讨论的内容。

  如何折叠多面体

  很早以前,Niklaus Wirth 撰写了一本名为《Algorithms+Data Structures=Programs》的著作,描述的是面向对象之前的内容,我猜想之前提到的 Charles Petzold 的那本书是它的改编本。即使在这么多年之后,它仍是我读过的最具影响力的书籍之一,虽然之后在语言和模式方面发生了许多变化,但它阐述的核心内容仍旧有效。该书的基本理念是开发方法应先确定模拟问题效果最好的数据结构,然后确定可处理或修改这些数据结构的算法。在解决非标准程序时,它是我经常采用的方法。

  我试遍了各种方法,最终才确定要在多面体中使用的一种。我想知道开始实际折叠动画时,所需的信息能精简到何种程度。结果证明,由于所有边的长度都相同,因此只要知道多面体中相连的面,几乎就万事大吉。由此可推出图形数据结构。在完成 XAML 输出之前,需使用一组算法来通过两个单独的树数据结构处理图形。稍后会对此加以介绍。

 选定形状后,会构建名为 Net(与 .NET 没有任何关系)的一个二维展开平板。此过程共分为两个阶段(如图 4 所示)。首先在内存中构建一个图形,一个 GraphNode 对应形状的一个面(请参阅 Graph.cs)。通过从嵌入应用程序集的一个 .shp 资源输入一组连接信息(指出哪些面相互连接)来构建该图形。例如,以下是 cube.shp 的内容:

Silverlight: 使用 Silverlight 2.0 构建高级 3D 动画

  Figure 4Building the Shape, from Graph to XAML

1:3,4,5,2
2:1,5,6,3
3:2,6,4,1
4:3,6,5,1 
5:1,4,6,2
6:2,5,4,3

 

  然后,可将图形中的任意 GraphNode 选作开始构建 Net 的首个节点(请参阅代码下载中的 Net.cs 获取详细信息)。然后布置一个二维 FlatFace,其边数与 GraphNode 的相邻点数量相同。接着,选择任意相邻的 GraphNode 来重复此过程,布置下一个 FlatFace 以便其与当前的 FlatFace 共享一个边。每个 GraphNode 仅访问一次,继续此过程直至访问完所有的 GraphNode,生成树结构的 FlatFace。

  在 3D 动画的每个帧中,通过 Net 构建一个三维多面体(可能部分闭合)(请参阅本文后面部分的 Polyhedron.cs 了解此操作的详细信息)。这需要将 FlatFace 树复制到 Face 树中,并用 3D 点替换 2D 角点。(图 5 显示了两个坐标系统。由于我希望从水平面开始,因此 Y 应从零开始。所以,通过设置 X=X、Y=0 和 Z=Y 来从 2D 映射到 3D。)

Silverlight: 使用 Silverlight 2.0 构建高级 3D 动画 

  尽管 Windows® Presentation Foundation (WPF) 可支持采用 XAML 的 3D,但 Silverlight 仅默认支持 2D,因为如果机器中的图形处理单元 (GPU) 硬件不会产生负面影响,这样就能轻松实现跨浏览器的兼容性。当然,如果深究的话,计算机屏幕上的 3D 是一个假象。无论使用何种操作,最终显示在显示器上的都是一组普通的 2D 多边形。如果要在代码中执行 3D 操作来计算这些多边形的坐标,可使用非硬件加速的 3D 呈现。假如多边形的数量不多,性能尚可接受。(从主观上讲,Alpha 到 Beta 的帧性能得到了改进。) 

 

  切勿尝试在同一 HTML 页面上使用多个单独的 Silverlight 控件。我实现的第一个多面体针对圆周中的每个示例使用了一个单独的控件,它实际占用了大量内存。在某些情况下,它可能意味着将内容从 HTML 移到 XAML 以减少所使用的控件数量。 

  通过使用 Silverlight 2.0 Beta,主初始化(即 InitComponents)已从 Loaded 事件处理程序变成了代码隐藏对象的构造函数。此方法更为妥当,但切记构造函数并不是万能的。例如,无法在 Storyboard 上调用 Begin 或 Pause,因此仍需通过事件处理程序来实现。 

  由于这些 Storyboard 可独立运行,因此每件事都可按您的预期发生,无论用户移动鼠标的速度是快还是慢。通常情况下,一个示例的 MouseLeave 动画会与新选择示例的 MouseEnter 动画同时发生。但是,不必担心,因为 Silverlight 会自动处理多个活动 Storyboard 的并行运行。 

 

  该 Path 还有一个名为 CycleRotate 的 RotateTransform。该转换最初不会执行任何操作,因为它的角度已设置为 0。但是,当名为 CycleLatched 的 Storyboard 被激活后,它会持续以很小的增量更改角度,从而使箭头开始旋转。 

 

  让我们来看看从 Page.xaml 摘录的两段代码以了解这些动画的实现方式,首先来看“Cycle”(循环)按钮(如图 2 所示)。其中有个名为 Cycle 的 Path。数据属性指定一系列操作,其中 M 代表 move(移动),A 代表 arc(弧形运动)且 L 代表 line(直线运动)。由于它只是一个相当简单的符号,因此我仅在一张纸上画出我希望的形状并手动设计所需操作。在大多数情况下,最好使用工具(如 Expression Design)来完成操作。 

  在 HTML 中,元素通常在各个 DIV 中分组,以便安排它们在页面上的位置。与此类似,在 XAML 中,形状在 Canvas(或 Canvas 类型的其他元素,如 Grid)中分组。就像 DIV 在 HTML 通常为嵌套格式一样,Canvas 在 XAML 中也可为嵌套格式。HTML 中的大多数元素都是矩形。然而,XAML 支持所有形状,包括 TextBlock、Rectangle、Polygon、Ellipse 以及非常灵活的 Path(它可实现用户定义的形状)。HTML 中的元素是通过 ID 属性来标识的,而 XAML 中用于标识元素的等价属性是 x:Name,其中 x 是 XAML 命名空间的别名。 

  使用 XAML 时也适合采用上述原则。组合内容的最快捷方法是尽可能多地使用 XAML 标记,并在必要时使用与 .NET 兼容的语言(如 C#)和 Silverlight Media API 来加以扩展。XAML 可以是手工编码、通过设计软件包(如 Expression BlendTM)生成、在开发期间由运行的程序产生,甚至在服务器上动态产生。理解这一点需要观念的改变。当 C# 程序员最后在非常适合使用 XAML 的实际环境中对功能进行编码时,则非常容易。 

copyrighthttp://msdn.microsoft.com/zh-cn/magazine/cc500570.aspx 

转载于:https://www.cnblogs.com/molin/archive/2009/11/22/silverlight_3d.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值