Bending the Cat: F# meets WPF 3D

I’ve been playing with F# and Windows Presentation Foundation (WPF a.k.a Avalon) and the combination is really nice. WPF comes with a new(ish) markup language called Xaml. This is another declarative language that works in a complimentary way to F#. Xaml allows you initialise an object graph into a specific state in a declarative style; moreover various designers will soon be come available to do this, so you won’t even have to hand code the Xaml file. You can then load the xaml file and use F# to tweak resulting images.

 

I have written a sample in which we create a 3D scene with one object, a photo, which rotates slowly though 180 degrees. The vast majority of this is written in Xaml, the only thing we do in F# is add the plane to scene and apply various effects to it.

 

Here are some of the resulting images; the rotation allows you to see the 3D effect clearly.

 







The code is downloadable here. Or can be seen below:

 

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

#I @"C:\Program Files\Reference Assemblies\Microsoft\WinFx\v3.0" ;;
#r @"PresentationCore.dll" ;;
#r @"PresentationFramework.dll" ;;
#r @"WindowsBase.dll" ;;

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

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

// loop function to create all items necessary for a plane
let create_plane_item_list f (x_res : int) (y_res : int) =
 let list = new List<_>() in
 for x = 0 to x_res - 1 do
  for y = 0 to y_res - 1 do
   f list x y
  done
 done;
 list

// function to initalise a point
let point x y = new Point(x, y)
// function to initalise 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 create_square f (x_step : float) (y_step : float) (list : List<_>) (x : int) (y : int) =
 let x' =  Float.of_int x * x_step in
 let y' =  Float.of_int y * y_step in
 list.Add(f x' y');
 list.Add(f (x' + x_step) y');
 list.Add(f (x' + x_step) (y' + y_step));
 list.Add(f (x' + x_step) (y' + y_step));
 list.Add(f x' (y' + y_step));
 list.Add(f x' y')

// create all items in a plane
let create_plane_points f x_res y_res =
 let x_step = 1.0 / Float.of_int x_res in
 let y_step = 1.0 / Float.of_int y_res in
 create_plane_item_list (create_square f x_step y_step) x_res y_res
 
// create the 3D positions for a plane, i.e. the thing that says where
// the plane will be in 3D space
let create_plane_positions x_res y_res =
 let list = create_plane_points point3D x_res y_res in
 new Point3DCollection(list)

// create the texture mappings for a plane, i.e. the thing that
// maps the 2D image to the 3D plane
let create_plane_textures x_res y_res =
 let list = create_plane_points point x_res y_res in
 new PointCollection(list)

// create indices list fora ll our triangles 
let create_indices_plane width height =
 let list = new System.Collections.Generic.List<int>() in
 for index = 0 to width * height * 6 do
  list.Add(index)
 done;
 new Int32Collection(list)

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

// create a plane and add it to the given mesh
let add_plane_to_mesh (mesh : MeshGeometry3D) x_res y_res =
 mesh.Positions <- map_positions_center (create_plane_positions x_res y_res);
 mesh.TextureCoordinates <- create_plane_textures x_res y_res;
 mesh.TriangleIndices <- create_indices_plane x_res y_res

// generic function for mapping Z plane relative to x y plane
let map_positions f (positions : Point3DCollection) =
 let new_positions = positions |> IEnumerable.map 
  (fun position ->
   new Point3D(position.X, position.Y, f position.X position.Y)) in
 new Point3DCollection(new_positions)

// map the plane's z coordiante using a cos wave of the y coordiante
let map_positions_cos_y =
 map_positions (fun _ y -> Math.Cos(y * Math.PI))

// map the plane's z coordiante using a cos wave of the x and y coordiantes
let map_positions_cos_xy =
 map_positions (fun x y -> Math.Cos(x * Math.PI) * Math.Cos(y * Math.PI))

// map the plane's z coordiante using a cos wave of the x and y coordiantes, that
// has been scaled to give a rippled effect
let map_positions_waves =
 map_positions (fun x y -> (Math.Cos(x * Math.PI * 4.0) / 3.0) * (Math.Cos(y * Math.PI * 2.0) / 3.0))

// map the plane's Z coordiante randomly
let map_positions_random =
 let rand = new Random() in
 map_positions (fun _ _ -> rand.NextDouble() / 10.0)

[<STAThread>]
do
 // create our window
 let window = create_window "Window1.xaml" in
 // grab the 3D view port
 let viewport = window.FindName("ViewPort") :?> Viewport3D in
 // find all the meshes and get the first one
 let meshes = find_meshes viewport in
 let mesh = IEnumerable.hd meshes in
 // add plane to the mesh
 add_plane_to_mesh mesh 20 20;
 // apply a transformation to the plane
 mesh.Positions <- map_positions_waves mesh.Positions;
 // show the window
 let app = new Application() in 
 app.Run(window) |> ignore

 


<Window
   
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
   
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="RichContent"
    >

  <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="-90" To="90" Duration="0:0:12"
               Storyboard.TargetName="MyRotation3D"
               Storyboard.TargetProperty="Angle" RepeatBehavior="Forever" AutoReverse="True" />
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger.Actions>
      </EventTrigger>
    </Viewport3D.Triggers>
  </Viewport3D>

</Window>

Bookmark
dotnetkicks+, digg+, reddit+, del.icio.us+, dzone+, facebook+

Print | posted @ Friday, June 23, 2006 9:20 AM

Comments on this entry:

Gravatar # re: Bending the Cat: F# meets WPF 3D
by Robert Pickering at 6/24/2006 7:04 AM

I have updated the code in the artical and in the download to shorten the find_meshes function. I know use:

|> IEnumerable.choose (function :? ModelVisual3D as c -> Some(c.Content) | _ -> None)
|> IEnumerable.choose (function :? Model3DGroup as mg -> Some(mg.Children) | _ -> None)
|> IEnumerable.concat
|> IEnumerable.choose (function :? GeometryModel3D as mg -> Some(x.Geometry) | _ -> None)
|> IEnumerable.choose (function :? MeshGeometry3D as mv -> Some(mv) | _ -> None)


Instead of

|> IEnumerable.filter (fun child -> (child :? ModelVisual3D))
|> IEnumerable.map (fun x -> (match x with | :? ModelVisual3D as mv -> mv | _ -> failwith "won't happen"))
|> IEnumerable.filter (fun x -> (x.Content :? Model3DGroup))
|> IEnumerable.map (fun x -> match x.Content with | :? Model3DGroup as mg -> mg | _ -> failwith "won't happen" )
|> IEnumerable.map (fun x -> x.Children )
|> IEnumerable.concat
|> IEnumerable.filter (fun x -> (x :? GeometryModel3D))
|> IEnumerable.map (fun x -> match x with | :? GeometryModel3D as mv -> mv | _ -> failwith "won't happen" )
|> IEnumerable.filter (fun x -> (x.Geometry :? MeshGeometry3D))
|> IEnumerable.map (fun x -> match x.Geometry with | :? MeshGeometry3D as mv -> mv | _ -> failwith "won't happen" )

Thanks to Don Syme for the suggestion
Gravatar # re: Bending the Cat: F# meets WPF 3D
by Alex Grenier at 8/23/2007 4:44 AM

Wow... pretty nice usage of some cutting edge stuff!
Do you have any tips on how can I get this to build and run under Orcas beta 2 after installing F# 1.9.2?
Cheers!
Gravatar # re: Bending the Cat: F# meets WPF 3D
by Robert Pickering at 8/23/2007 9:51 AM

Thanks!

Yes you can find out how to fix F#/VS Orcas here:
http://gbarnett.org/archive/2007/08/03/how-to-get-f-working-on-vs-2008-beta-2.aspx
Gravatar # re: Bending the Cat: F# meets WPF 3D
by Alex Grenier at 8/24/2007 3:49 AM

Thanks!

Once F# properly registered with Orcas, all I had to do is point to the 3.0 WPF assemblies on this Longhorn box. I suppose that's the path of 3.0 when bundled with the OS.

So compile options become:

-I "C:\Program Files\Reference Assemblies\Microsoft\Framework\v3.0" -r "PresentationCore.dll" -r "PresentationFramework.dll" -r "WindowsBase.dll"

Instead of:

-I "C:\Program Files\Reference Assemblies\Microsoft\WinFX\v3.0" -r "PresentationCore.dll" -r "PresentationFramework.dll" -r "WindowsBase.dll"

Gravatar # re: Bending the Cat: F# meets WPF 3D
by pocky at 3/1/2008 12:38 PM

good sample and if we need to load many xaml files to one scence, How can we do ?

Your comment:

(Note: all comments are moderated so it may take sometime to appear)

Title:
Name:
Email:
Website:
 
Italic Underline Blockquote Hyperlink
 
 
Please add 3 and 7 and type the answer here: