介绍 Windows Presentation Foundation 3D

来源:互联网 发布:mysql数据库汉化版 编辑:程序博客网 时间:2024/05/16 23:44

介绍 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.dllPresentationFramework.dll WindowsBase.dll,还要加两个System.Xml.dllSystem.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 前面的 ..\..\ 也就不需要了。

]

 

0 0
原创粉丝点击