原文:在WPF中使用PlaneProjection模拟动态3D效果
虽然在WPF中也集成了3D呈现的功能,在简单的3D应用中,有时候并不需要真实光影的3D场景。毕竟使用3D引擎会消耗很多资源,有时候使用各种变换和假的阴影贴图也能设计出既省资源,又有很好用户体验的“伪”3D界面。
在Silverlight中,因为性能问题,一般并不使用真3D引擎,微软为Silverlight提供了System.Windows.Media.PlaneProjection 类,用投影变换来模拟3D的效果。
下面让我们看下一个 Microsoft Expression Blend 4 提供的示例 Wall3D (位于帮助>欢迎屏幕>示例)。
大家不要被这个可以流畅滚动的3D图片墙所迷惑,其实这只是一个ListBox控件。MainPage中给ListBox定义了一个ItemsPanelTemplate,使用新的控件来作为ListBox中Items的布局控件,这个
控件就是这个项目最核心的类:CircularPanel3D。
CircularPanel3D类继承自System.Windows.Controls.Panel,它实现了一种新的布局方式,效果大家在上一张图片中都看到了。这种华丽的效果实际上都是由这个最重要的类中的最重要的方法:
private void Refresh() 完成的。
1 privatevoid Refresh()
2 {
3 //几个计数器,看名字就功能很明了
4 int count =0;
5 int col =0;
6 int row =0;
7 int zLevel =0;
8
9 //开始遍历子元素
10 foreach (FrameworkElement childElement inthis.Children)
11 {
12 //AngleItem是指单个元素的旋转角度,算法是360除以列数
13 //这个方法的布局方式是先布满一圈,再下一环的,角度总是可以取模的
14 //所以这边直接AngleItem和count相乘了
15 //InitialAngle这个属性是用来确定整个圆环的偏转角度的,每次这个依赖属性变化就会重新计算布局(调用这个方法)
16 double angle = (this.AngleItem * count++) -this.InitialAngle;
17 //下面两个变量用来确定元素在屏幕上的位置,用到了三角函数,数学不好的请问高中数学老师
18 double x =this.Radius * Math.Cos(Math.PI * angle /180);
19 double z =this.Radius * Math.Sin(Math.PI * angle /180);
20 //创建个PlaneProjection对象,并赋值
21 PlaneProjection projection =new PlaneProjection();
22 if (projection !=null)
23 {
24 projection.CenterOfRotationX =0.5;
25 projection.CenterOfRotationY =0.5;
26 projection.CenterOfRotationZ =0.5;
27 projection.RotationY = angle +90;
28 projection.GlobalOffsetX = x;
29 //Distance实际上就是模拟的镜头距离
30 projection.GlobalOffsetZ = z -this.Distance;
31 //-330。。。坑爹的硬编码,实际上就是两行元素的间距,OffsetY是纵向的偏移量,用于调整环在屏幕上的位置
32 projection.GlobalOffsetY = row * (-330) +this.OffsetY;
33 }
34 //实际上是让double数变成int数,但是又不会丧失区别性,下面要用到
35 int depth = (int)(z *100);
36
37 double pDist = (this.Distance -1000) /2000;
38 double pZ = ((z +1000) /2000) +0.5;
39
40 //让太远的和太近的变透明
41 double opacity = (pZ - pDist) +0.4;
42 if (opacity >=1)
43 {
44 childElement.Opacity = (2- opacity);
45 }
46 elseif (opacity <0)
47 {
48 childElement.Opacity =0;
49 }
50 else
51 {
52 childElement.Opacity = opacity;
53 }
54
55 // 嗯这边有原版的英文注释,不解释
56 // Variable zLevel changes value of ZIndex for each item in the ListBox.
57 // This way the reflex of elements at the top will be placed behind the item below it.
58 Canvas.SetZIndex(childElement, depth-(++zLevel*10));
59
60 //根据Align属性设置对齐方式,不是很重要
61 double alignX =0;
62 double alignY =0;
63 switch (this.Align)
64 {
65 case AlignmentOptions.Left:
66 alignX =0;
67 alignY =0;
68 break;
69 case AlignmentOptions.Center:
70 alignX = childElement.DesiredSize.Width /2;
71 alignY = childElement.DesiredSize.Height /2;
72 break;
73 case AlignmentOptions.Right:
74 alignX = childElement.DesiredSize.Width;
75 alignY = childElement.DesiredSize.Height;
76 break;
77 }
78 //将PlaneProjection对象赋给子元素的Projection属性
79 childElement.Projection = projection;
80 //定位子元素
81 childElement.Arrange(new Rect(this.Width /2- alignX , this.Height /2- alignY, childElement.DesiredSize.Width, childElement.DesiredSize.Height));
82
83 //换行,又见坑爹的硬编码14。。这个代表有十四列
84 col++;
85 if (col >14)
86 {
87 col =0;
88 row++;
89 }
90 }
91 }