I was playing around with implementing drawing a Mandelbrot set in F#. F# makes this very easy because of its Microsoft.FSharp.Math namespace provides a very easy to use complex implementation which means you just need to type out the equation and viola you have the Mandelbrot set. Or so I thought. My implementation end up being about 40 lines about 11 of which were the equation on which the Mandelbrot set was based and the rest of the code was infrastructure to use the equation and display the results. While the resulting code, shown below, is still reasonably short this isn’t what I was hopping for.

<?xml:namespace prefix = o ns = “urn:schemas-microsoft-com:office:office” />/o:p 

open System
open System.Drawing
open System.Windows.Forms
open Microsoft.FSharp.Math

let c_max = Complex.one + Complex.onei
let c_min = Complex.mkRect(-1.0, -1.0)
let iterantions = 18.0

let is_in_mandelbrot_set x y =
    let c = ref (Complex.mkRect(x,y)) in
    let count = ref 0.0 in
    let const_c = !c in
    while (c_max > !c) && (!c > c_min) && (!count < iterantions) do
        c := (!c * !c) + const_c;
        count := !count + 1.0
    done;
    !count

let intergral = 1.0 / 200.0
let offset = -1.0

let get_coord (x, y) =
     let fx = ((float_of_int x) * intergral) + offset in
     let fy = ((float_of_int y) * intergral) + offset in
     fx, fy
        
let form =
    let image = new Bitmap(400, 400) in
    for x = 1 to 399 do
        for y = 1 to 399 do
            let fx, fy = get_coord (x, y) in
            let no = is_in_mandelbrot_set fx fy in
            if no = iterantions then
                image.SetPixel(x,y, Color.Black)
        done
    done;
    let temp = new Form() in
    temp.Paint.Add(fun e -> e.Graphics.DrawImage(image, 0, 0));
    temp   

[]   
do Application.Run(form)
 /o:p

/o:p 

The resulting picture is pretty dull too, while this is clearly the Mandelbrot set, there are no jazzy colours and there’s no zoom./o:p

 /o:p

But let’s not get too down on this implementation, let look at what F# did well, the 11 lines that made up the equation that tests whether a point is part of the Mandelbrot set or not.  So here’s the bit I like. I reasoned that as it was the infrastructure code that was taking up the space, I could import the Mandelbrot code into the infrastructure I made to show how F# worked with WPF/Xaml. All I did was copied and pasted the “is_in_mandelbrot_set” function into my cat sample. I then created a new function “map_positions_mandelbrot” that mapped the WFP class Point3DCollection to a Point3DCollection where the Z index had been altered according to the “is_in_mandelbrot_set” function, this sounds complex but using the existing infrastructure it took just two lines of code. Then it was possible to run the example and view the distinctive Mandelbrot shape mapped over a 3D plane containing a picture of my cat. Pretty neat for just 20 extra lines of code, including comments./o:p

 /o:p

I also added a couple of slide bars to allow the user to zoom in and out of the plane and also twist it around to view it from other angles. This was also very easy positioning the sliders on the window was done in Xaml so hidden from the F# code, then it was just a matter of attaching a two line function to the relivant event handler and the zoom and twist was taken care of./o:p

 /o:p

The full F# code is listed here:

/o:p 

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
open Microsoft.FSharp.Math

#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) (yres : 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) (ystep : 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)

// constants for the mandelbrot function
let c_max = Complex.one + Complex.onei
let c_min = Complex.mkRect(-1.0, -1.0)
let iterantions = 50.0

let is_in_mandelbrot_set x y =
    let c = ref (Complex.mkRect(x * 2.0, y * 2.0)) in
    let count = ref 0.0 in
    let const_c = !c in
    while (c_max > !c) && (!c > c_min) && (!count < iterantions) do
        c := (!c * !c) + const_c;
        count := !count + 1.0
    done;
    !count / (iterantions * 3.0)

// the mandelbrot function itself
let map_positions_mandelbrot =
 map_positions is_in_mandelbrot_set

[]
do
 // create our window
 let window = create_window “Window1.xaml” in
 // grab the 3D view port
 let viewport = window.FindName(“ViewPort”) :?> Viewport3D in
 let perspectiveCamera =  viewport.Camera :?> PerspectiveCamera in
 // find the zoom slider and attach an event handler to it
 let zoom = window.FindName(“Zoom”) :?> Slider in
 zoom.Value <- 4.0;
 zoom.ValueChanged.Add(fun e -> perspectiveCamera.FieldOfView <- (e.NewValue * 10.0) );
 // find the angel slider and attach an event handler to it
 let angel = window.FindName(“Angel”) :?> Slider in
 angel.Value <- 5.0;
 angel.ValueChanged.Add(
  fun e ->
   perspectiveCamera.LookDirection <- new Vector3D(0.0,(e.NewValue - 5.0) / -2.0,-1.0);
   perspectiveCamera.Position <- new Point3D(0.0,(e.NewValue - 5.0),2.0));
 // 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 100 100;
 // apply a transformation to the plane
 mesh.Positions <- map_positions_mandelbrot mesh.Positions;
 // show the window
 let app = new Application() in
    app.Run(window) |> ignore
 /o:p

/o:p 

The Xaml code is here:/o:p

   
<Window
   
xmlns=”http://schemas.microsoft.com/winfx/2006/xaml/presentation
   
xmlns:x=”http://schemas.microsoft.com/winfx/2006/xaml
    Title=“RichContent”
    >
        <Grid>
          <Grid.ColumnDefinitions>
            <ColumnDefinition Width=“64” />
            <ColumnDefinition Width=”” />
          </Grid.ColumnDefinitions>
          <Grid.RowDefinitions>
            <RowDefinition Height=“24”/>
            <RowDefinition Height=“24”/>
            <RowDefinition Height=”
” />
          </Grid.RowDefinitions>
  <Label Grid.Row=“0” Grid.Column=“0” >Zoom</Label>
  <Label Grid.Row=“1” Grid.Column=“0” >Angel</Label>
  <Slider Name=“Zoom” Grid.Row=“0” Grid.Column=“1” />
  <Slider Name=“Angel” Grid.Row=“1” Grid.Column=“1” />
  <Viewport3D Name=“ViewPort” Grid.Row=“2” Grid.Column=“0” Grid.ColumnSpan=“2” >

    <Viewport3D.Camera>
      <PerspectiveCamera Position=“0,0,2” LookDirection=“0,0,-1” FieldOfView=“40” />
    </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=“0” Axis=“1,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=“-45” To=“45” Duration=“0:0:12”
               Storyboard.TargetName=“MyRotation3D”
               Storyboard.TargetProperty=“Angle” RepeatBehavior=“Forever” AutoReverse=“True” />
            </Storyboard>
          </BeginStoryboard>
        </EventTrigger.Actions>
      </EventTrigger>
    </Viewport3D.Triggers>

  </Viewport3D>
    </Grid>

</Window>


/o:pIt is available for download along with the original Mandelbrot sample here./o:p