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>