您的位置:首页 > 其它

介绍 Windows Presentation Foundation 3D

2014-05-06 21:37 381 查看
介绍 Windows Presentation Foundation 3D

WPF 的另一个巨大优势在于它提供了大量的控件,我们将要深入探讨的一个控件是 Viewport3D,它能创建优秀的三维图形,其中的一些功能在 Windows 窗体库中还没有。我们将学习用这个控件显示三维display a 3D plane and then map an equation over it.

这个例子 [ 见后面的清单 8-7 ] 首选有一个 XAML 脚本。XAML 和 3D 图形都是巨大的主题,我们不打算详细讨论,但仍会学习足够的知识,了解所涉及的概念,当然,你也可能自己再专门研究这些主题。下面的 XAML 脚本描述了有一个控件 Viewport3D 的窗口,这个脚本相当长,因为构成一个三维场景需要很多元素。我们首先定义一个照相机,这样,可以知道你正在看场景中的哪一个方向,这需要用到 <Viewport3D.Camera> 元素:

<Viewport3D.Camera>
<PerspectiveCamera Position="0,0,2" LookDirection="0,0,-1"
FieldOfView="60"/>
</Viewport3D.Camera>

在标记 <Model3DGroup> 内描述了场景的外观,<AmbientLight Color="White" /> 描述如何照亮场景,<GeometryModel3D.Geometry>描述场景中的三维形状。

<GeometryModel3D.Geometry>
<MeshGeometry3D />
</GeometryModel3D.Geometry>

这里,可以用 <MeshGeometry3D /> 标记描述组成场景的所有对象,只要提供组成对象的专门点。然而,你不一定使用这个标记来描述组成形状的点,因为,用 F# 比 XAML 更容易实现。<GeometryModel3D.Material> 标记描述形状的外观:

<GeometryModel3D.Material>
<DiffuseMaterial>
<DiffuseMaterial.Brush>
<ImageBrush ImageSource="venus.jpg"/>
</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>

<GeometryModel3D.Transform> 标记描述形状的变换,即,描述开头旋转一个角度:

<GeometryModel3D.Transform>
<RotateTransform3D>
<RotateTransform3D.Rotation>
<AxisAngleRotation3D x:Name="MyRotation3D"
Angle="45" Axis="0,1,0"/>
</RotateTransform3D.Rotation>
</RotateTransform3D>
</GeometryModel3D.Transform>

可以用 <Viewport3D.Triggers> 标记去定义动画,随时间而改变开头的角度:

<Viewport3D.Triggers>
<EventTrigger RoutedEvent="Viewport3D.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
From="-80"
To="80"
Duration="0:0:12"
Storyboard.TargetName="MyRotation3D"
Storyboard.TargetProperty="Angle"
RepeatBehavior="Forever"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Viewport3D.Triggers>

清单 8-7 提供了完整的代码,可以看到这些部分是如何组织在一起的。

清单 8-7定义三维场景的 XAML

<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Viewport3D Name="ViewPort">
<Viewport3D.Camera>
<PerspectiveCamera Position="0,0,2" LookDirection="0,0,-1"
FieldOfView="60"/>
</Viewport3D.Camera>
<Viewport3D.Children>
<ModelVisual3D>
<ModelVisual3D.Content>
<Model3DGroup >
<Model3DGroup.Children>
<AmbientLight Color="White"/>
<GeometryModel3D>
<GeometryModel3D.Geometry>
<MeshGeometry3D />
</GeometryModel3D.Geometry>
<GeometryModel3D.Transform>
<RotateTransform3D>

<RotateTransform3D.Rotation>
<AxisAngleRotation3D

x:Name="MyRotation3D"

Angle="45"

Axis="0,1,0"/>

</RotateTransform3D.Rotation>
</RotateTransform3D>
</GeometryModel3D.Transform>
<GeometryModel3D.Material>
<DiffuseMaterial>

<DiffuseMaterial.Brush>

<ImageBrush ImageSource="venus.jpg"/>

</DiffuseMaterial.Brush>
</DiffuseMaterial>
</GeometryModel3D.Material>
</GeometryModel3D>
</Model3DGroup.Children>
</Model3DGroup>
</ModelVisual3D.Content>
</ModelVisual3D>
</Viewport3D.Children>
<Viewport3D.Triggers>
<EventTrigger RoutedEvent="Viewport3D.Loaded">
<EventTrigger.Actions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation
From="-80"
To="80"
Duration="0:0:12"
Storyboard.TargetName="MyRotation3D"
Storyboard.TargetProperty="Angle"
RepeatBehavior="Forever"
AutoReverse="True"/>
</Storyboard>
</BeginStoryboard>
</EventTrigger.Actions>
</EventTrigger>
</Viewport3D.Triggers>
</Viewport3D>
</Window>

下面将用 F# 对清单 8-7 进行扩展,在清单 8-8 中,它借用了清单 8-6 中的函数。注意,清单 8-7 中的代码应该保存文件名为 Window2.xaml。用 createWindow 函数加载窗口,用类型的主函数显示窗口;然后,用 findMeshes 函数找出图中所有网络(meshes,网络是用于描述三维空间的一组点)。通过在 Viewport3D 中遍历各种对象,找出网络,并建立一个列表:

//finds all the MeshGeometry3D in a given 3D view port
let findMeshes (viewport : Viewport3D ) =
viewport.Children
|> Seq.choose
(function :? ModelVisual3D
as c -> Some(c.Content) | _-> None)
|> Seq.choose
(function :? Model3DGroup
as mg -> Some(mg.Children) |_
-> None)
|> Seq.concat
|> Seq.choose
(function :? GeometryModel3D
as mg -> Some(mg.Geometry) |_
-> None)
|> Seq.choose
(function :? MeshGeometry3D
as mv -> Some(mv) | _
-> None)

应该保持这个函数为泛型,它才能处理任意 Viewport3D。我们很可能想三维场景中的所有网格抓取到一个列表中,用 XAML 和 F# 进行三维处理,其原因是:很可能想用 F# 以某种方式来操纵网格;然后,用 createPlaneItemList、createSquare、createPlanePoints、createIndicesPlane 和 addPlaneToMesh,在场景中添加平面到这个网格。函数 mapPositionsCenter 使平面在场景中居中;最后,一个聪明的小函数 changePositions,重复映射函数
movingWaves,每秒钟穿过平面 10 次。这个函数的核心是从 Point3D 对象创建一个新的 Point3Dcollection,老集合中的对象用函数 movingWaves 决定新的 Z 位置:

let changePositions ()=
let dispatcherTimer =
new DispatcherTimer()
dispatcherTimer.Tick.Add
(fun e
->
let t = (float DateTime.Now.Millisecond) / 2000.0
let newPositions =
mesh.Positions
|> Seq.map
(fun position
->
let z = movingWaves tposition.X position.Y
new Point3D(position.X,position.Y, z))
mesh.Positions <- newPoint3DCollection(newPositions))
dispatcherTimer.Interval <- newTimeSpan(0,0,0,0,100)
dispatcherTimer.Start()

使用 DispatcherTimer 类运行线程中代码,创建窗体,即,不需要回调这个线程来更新窗体。为创建平滑的动画效果,每秒钟至调用这个类 10 次(清单 8-8 是完整的示例)。

清单 8-8 显示三维场景

open System
openSystem.Collections.Generic
open System.IO
open System.Windows
openSystem.Windows.Controls
open System.Windows.Markup
openSystem.Windows.Media
openSystem.Windows.Media.Media3D
openSystem.Windows.Threading
open System.Xml

//creates the window and loads the given XAML file into it
let createWindow (file:
string) =
using (XmlReader.Create(file))
(fun stream
->
let temp =
XamlReader.Load(stream) :?> Window
temp.Height <- 400.0
temp.Width <- 400.0
temp.Title <- "F# meetsXaml"
temp)

//finds all the MeshGeometry3D in a given 3D view port
let findMeshes (viewport :
Viewport3D ) =
viewport.Children
|> Seq.choose
(function :?
ModelVisual3D as c
-> Some(c.Content) | _ -> None)
|> Seq.choose
(function :?
Model3DGroup as mg
-> Some(mg.Children) | _ -> None)
|> Seq.concat
|> Seq.choose
(function :?
GeometryModel3D as mg
-> Some(mg.Geometry) | _ -> None)
|> Seq.choose
(function :?
MeshGeometry3D as mv
-> Some(mv) | _ -> None)

//loop function to create all items necessary for a plane
let createPlaneItemListf (xRes :
int) (yRes : int) =
let list =
new List<_>()
for x = 0
to xRes - 1 do
for y = 0
to yRes - 1 do
f list x y
list

//function to initialize a point
let point x y =
new Point(x, y)

//function to initialize a "d point
let point3D x y =
new Point3D(x, y, 0.0)

//create all the points necessary for a square in the plane
let createSquare
f (xStep : float) (yStep :float) (list :
List<_>) (x :
int) (y : int) =
let x' = float x * xStep
let y' = float y * yStep
list.Add(f x' y')
list.Add(f (x' + xStep) y')
list.Add(f (x' + xStep) (y' + yStep))
list.Add(f (x' + xStep) (y' + yStep))
list.Add(f x' (y' + yStep))
list.Add(f x' y')

//create all items in a plane
let createPlanePoints fxRes yRes =
let xStep = 1.0 / float xRes
let yStep = 1.0 / float yRes
createPlaneItemList (createSquare f xStepyStep) xRes yRes

//create the 3D positions for a plane, i.e., the thing that says where
//the plane will be in 3D space
letcreatePlanePositions xRes yRes =
let list = createPlanePoints point3D xRes yRes
new
Point3DCollection(list)

//create the texture mappings for a plane, i.e., the thing that
//maps the 2D image to the 3D plane
let createPlaneTexturesxRes yRes =
let list = createPlanePoints point xRes yRes
new
PointCollection(list)

//create indices list for all our triangles
let createIndicesPlanewidth height =
let list =
new System.Collections.Generic.List<int>()
for index = 0
to width * height * 6 do
list.Add(index)
new
Int32Collection(list)

//center the plane in the field of view
let mapPositionsCenter(positions :Point3DCollection) =
let newPositions =
positions
|> Seq.map
(fun position
->
new
Point3D(
(position.X - 0.5 ) * -1.0,
(position.Y - 0.5 ) * -1.0,
position.Z))
new
Point3DCollection(newPositions)

//create a plane and add it to the given mesh
let addPlaneToMesh(mesh :
MeshGeometry3D) xRes yRes =
mesh.Positions <- mapPositionsCenter
(createPlanePositions xRes yRes)
mesh.TextureCoordinates <-createPlaneTextures xRes yRes
mesh.TriangleIndices <- createIndicesPlanexRes yRes

let movingWaves (t :
float) x y =
(Math.Cos((x + t) *
Math.PI * 4.0) / 3.0) *
(Math.Cos(y *
Math.PI * 2.0) / 3.0)

//create our window
let window =createWindow
"..\..\Window2.xaml"

let mesh =
// grab the 3D view port
let viewport = window.FindName("ViewPort") :?>Viewport3D
// find all the meshes and get the first one
let meshes = findMeshes viewport
let mesh =
Seq.head meshes
// add plane to the mesh
addPlaneToMesh mesh 20 20
mesh

let changePositions ()=
let dispatcherTimer =
new DispatcherTimer()
dispatcherTimer.Tick.Add
(fun e
->
let t = (float
DateTime.Now.Millisecond) /2000.0
let newPositions =
mesh.Positions
|> Seq.map
(fun position
->
let z = movingWaves tposition.X position.Y
new
Point3D(position.X,position.Y, z))
mesh.Positions <- new
Point3DCollection(newPositions))
dispatcherTimer.Interval <- newTimeSpan(0,0,0,0,100)
dispatcherTimer.Start()

let main() =
let app =
new Application()
changePositions()
// show the window
app.Run(window) |> ignore

[<STAThread>]
do main()

[

注:需要引用添加引用 PresentationCore.dll、PresentationFramework.dll和
WindowsBase.dll,还要加两个System.Xml.dll、System.Xaml.dll

注意一下Window1.xaml 路径的问题,加上 ..\..\

]

前面代码的运行结果如图 8-8 所示。它并不会显示动画效果,建议自己尝试运行这个程序,看一下动画效果。



图 8-8 用 XAML 和 F# 创建的三维场景

另外,建议你在交互环境中运行这个应用程序。只需要稍微修改一下程序就可以在交互环境中运行了,然后,可以动态地更改应用于平面的函数。原始的脚本有几处要做小的修改。

首先,必须以交互风格引用 .dll 文件:

//#I
@"C:\ProgramFiles\Reference Assemblies\Microsoft\Framework\v3.0" ;;
#r
@"PresentationCore.dll" ;;
#r
@"PresentationFramework.dll" ;;
#r
@"WindowsBase.dll" ;;
#r
@"System.Xaml";;
#r
@"System.Xml";;

然后,把changePositions 函数修改为能够使用可变函数:

//mutable function that is used within changePositions function
let mutable f = (fun (t :float) (x :
float) (y :
float) -> 0.0)

//function for changing the plane over time
let changePositions ()=
let dispatcherTimer =
new DispatcherTimer()
dispatcherTimer.Tick.Add
(fun e
->
let t = (float
DateTime.Now.Millisecond) /2000.0
let newPositions =
mesh.Positions
|> Seq.map
(fun position
->
let z = f t position.Xposition.Y
new
Point3D(position.X,position.Y, z))
mesh.Positions <- new
Point3DCollection(newPositions))
dispatcherTimer.Interval <- newTimeSpan(0,0,0,0,100)
dispatcherTimer.Start()

最后,用 .Show() 方法显示窗口,不能用 Application 类的 Run 方法了。不要忘记设置 Topmost 属性为 true,这样,能够更方便与窗口交互,并看到结果:

//show the window, set it the top, and activate the function that will
//set it moving
window.Show()
window.Topmost<- true
changePositions()

最后[ 怎么又是最后,接下来,],需要定义一些函数来映射跨平面。它可以是任意函数,有三个浮点数的参数(第一个表示时间,后两个分别表示 X、Y 的座标),返回值也是浮点数,表示 Z 座标。我非常喜欢使用正弦和余弦函数,可以生成有趣的波形。下面的代码是几个可用的示例,也可以根据自己的喜好随时创造:

let cosXY _ x y =
Math.Cos(x *
Math.PI) * Math.Cos(y *
Math.PI)

let movingCosXY (t :
float) x y =
Math.Cos((x + t) *
Math.PI) * Math.Cos((y - t) *
Math.PI)

然后,可以通过修改这个可变函数,很容易地应用这些函数:

f<- cosXY
f<- movingCosXY

使用这项技术产生的图像,如图 8-9。



图 8-9 以交互方式控制三维 XAML 场景的交互性

WPF 框架包含大量的类型与控件值得程序员花时间去学习。幸运的是,互联网上有许多资源,比较好的站点有,NetFx3 WPF (http://wpf.netfx3.com),还有MSDN 中的 WPF (http://msdn2.microsoft.com/en-us/netframework/aa663326.aspx)。

[

1、不需要 #I 指令;

//#I@"C:\Program Files (x86)\ReferenceAssemblies\Microsoft\Framework\.NETFramework\v4.5" ;;
2、设置当前目录为脚本目录;
//sets the current directory to be same as the script directory
System.IO.Directory.SetCurrentDirectory(__SOURCE_DIRECTORY__)
3、这样, Window2.xaml 前面的 ..\..\ 也就不需要了。

]
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: