Referencing DirectX Libraries in an F# Application
Despite the fact that WPF has a more powerful graphics engine that Windows Forms, developing commercial game software is accomplished via DirectX or, in some cases, the OpenGL. Microsoft provides a high-level interface to DirectX from the .NET Framework: Managed DirectX. Even though this a high-level interface, programs using Managed DirectX contain a significant amount of "boiler plate" code that is required to get anything working. This article will focus on writing F# code to draw from that Managed DirectX reusable libraries. When using Visual Studio, F# code is normally tested by highlighting that code to the press Alt-Enter to send the code into the F# interactive. The directory that contains these DLLs is C:\Windows\Microsoft.NET\DirectX for Managed Code\1.0.2902.0. Therefore if we use F#, a functional programming language that is said to have significant future, we could load the libraries to an include by doing this:
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2902.0"
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2903.0"
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2904.0"
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2907.0"
Those lines specify the include path and are the equivalent of the -I
switch to the F# compiler. Now having specified those paths, we would then reference the appropriate DLLs:
#r @"Microsoft.DirectX.dll"
#r @"Microsoft.DirectX.Direct3D.dll"
#r @"Microsoft.DirectX.Direct3Dx.dll"
Those specify the DLL reference, the equivalent to the -r
command line option. As with any Visual Studio managed code solution container, we would also right-click the references, browse to the folder containing those DLLs, and add them to the references section. The program we are going to examine is user interactive, having mouse-clicks perform some basic adjustments in the position of the graphical display. This collection of shapes will work to exhibit motion depicts animation, but is normally not documented that way. But how to managed source code files to execute the F# file that contains the above specification for referencing the DLLs? We use the load command:
#load @"BindAsLegacyV2Runtime.fs"
#load @"dxlib.fs"
These two files are contained in the solution to load into the interactive upon executing the Script.fsx file. Normally it would not make sense to highlight lines or blocks one at a time in order to build an executable, but doing this results in a remarkable graphics display DirectX style.
This sample contains a script that begins by guiding the user through setting up a DirectX enabled window suitable for rendering 3D functions. Then the user is shown how to plot and animate several functions of varying complexity. Finally, physics routines are provided that allow the user to simulate objects sliding around the surface of the plotted curves. Some helpful utility functions that help setup the window, perform some of the matrix calculations, and handle vertex coloring are provided in a module outside of the script file.
When the image loads, you will see movement that transforms the entire scene. Take your mouse and the left-mouse key down to examine these effects. Executing this code requires using Visual Studio 2008 or 2010, and the most recent DirectX SDK. Let’s examine the Script.fsx file (recall that in order to execute it, you highlight the code, press Alt-Enter to send it the interactive, and it will compile:
#load @"BindAsLegacyV2Runtime.fs"
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2902.0"
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2903.0"
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2904.0"
#I @"C:\WINDOWS\Microsoft.NET\DirectX for Managed Code\1.0.2907.0"
#r @"Microsoft.DirectX.dll"
#r @"Microsoft.DirectX.Direct3D.dll"
#r @"Microsoft.DirectX.Direct3Dx.dll"
#load @"dxlib.fs"
open System
open System.Drawing
open System.Windows.Forms
open Microsoft.DirectX
open Microsoft.DirectX.Direct3D
open Microsoft.FSharp.Control.CommonExtensions
open Sample.DirectX
open Sample.DirectX.MathOps
open Sample.DirectX.VectorOps
let form = new SmoothForm(Visible = true, TopMost = true,
Text = "F# surface plot",
ClientSize = Size(600,400),
FormBorderStyle=FormBorderStyle.FixedSingle)
let renderer = new DirectXRenderer(form)
renderer.DrawScene.Add(fun _ -> renderer.DrawCubeAxis())
renderer.DrawScene.Add(fun _ -> renderer.SetupLights())
let mutable view =
{ YawPitchRoll = Matrix.RotationYawPitchRoll(0.0f,0.0f,0.0f);
Focus = scale 0.5f (X1 + Y1 + Z1);
Zoom = 4.0 }
renderer.DrawScene.Add(fun _ -> renderer.SetView(view))
let mouseTrack = MouseTracker(form)
mouseTrack.Add(fun (a,b) ->
let view2 =
let dx = b.X - a.X
let dy = b.Y - a.Y
match b.Button, Form.ModifierKeys with
| MouseButtons.Left, Keys.Shift -> view.AdjustZoom(dx,dy)
| MouseButtons.Left, _ -> view.AdjustYawPitchRoll(dx,dy)
| _ -> view.AdjustFocus(dx,dy)
view <- view2
)
let mutable ff = (fun (t:float32) x y -> x * (1.0f - y))
let mutable range = (0.0f,1.0f)
let mutable mesh = BaseMesh.Grid(20,20)
let scalef (min,max) (z:float32) = (z-min) / (max-min)
let theFunction t x y = ff t x y |> scalef range
renderer.DrawScene.Add(fun t -> renderer.DrawSurface mesh (theFunction t))
ff <- (fun t x y -> sqr (x - 0.5f) * sqr (y - 0.5f) * 16.0f)
ff <- (fun t x y -> 0.5f * sin(x * 4.5f + t / 2.0f) * cos(y * 8.0f) * x + 0.5f)
range <- (-1.0f,1.0f)
range <- (0.0f,1.0f)
let ripple t x y =
let x,y = x - 0.5f,y - 0.5f
let r = sqrt (x*x + y*y)
exp(-5.0f * r) * sin(6.0f * pi * r + t) + 0.5f
ff <- ripple
mesh <- BaseMesh.Grid (50,50)
mesh <- BaseMesh.Grid (20,20)
let surfacePoint f x y = Vector3(x,y,f x y)
let surfaceNormal f x y =
let dx,dy = 0.01f,0.01f
let pA = surfacePoint f x y
let pA_dx = surfacePoint f (x+dx) y - pA
let pA_dy = surfacePoint f x (y+dy) - pA
normalize (cross pA_dx pA_dy)
let gravity = Vector3(0.0f,0.0f,-9.81f)
type ball = Ball of Vector3 * Vector3
let radiusA = 0.010f
let radiusB = 0.005f
let moveBall f timeDelta (Ball (position,velocity)) =
let nHat = surfaceNormal f position.X position.Y
let acc = planeProject nHat gravity let velocity = planeProject nHat velocity
let position = position + Vector3.Scale(velocity,timeDelta) let velocity = velocity + Vector3.Scale(acc ,timeDelta)
let bounce (p,v) =
if (p < 0.0f + radiusA) then (2.0f * (0.0f + radiusA) - p,-v)
elif (p > 1.0f - radiusA) then (2.0f * (1.0f - radiusA) - p,-v)
else (p,v)
let px,vx = bounce (position.X,velocity.X) let py,vy = bounce (position.Y,velocity.Y) let position = surfacePoint f px py let velocity = Vector3 (vx,vy,velocity.Z)
let velocity = planeProject nHat velocity
Ball (position,velocity)
let drawBall t (Ball (p,v)) =
let n = surfaceNormal (theFunction t) p.X p.Y
let p0 = Vector3(p.X,p.Y,0.0f)
let pV = Vector3(v.X,v.Y,0.0f)
let pVxZ = Vector3.Cross(pV,Z1)
renderer.DrawLines (Array.map (Vertex.Colored Color.Gray) [| p0;p |])
renderer.DrawPlaneArrow Z1 p0 pV
renderer.DrawPlaneArrow (cross n X1) p (scale 0.8f n)
renderer.Device.Transform.World <-
(let m = Matrix.LookAtLH(p + scale radiusB n,p+n,X1)
Matrix.Invert(m))
using (Mesh .Torus(renderer.Device,radiusB,radiusA,20,20)) (fun mesh ->
mesh.ComputeNormals()
mesh.DrawSubset(0))
renderer.Device.Transform.World <- Matrix.Identity
let mutable active = [] : ball list
let addBall ball = active <- (ball :: active)
let drawBalls t = active |> List.iter(drawBall t)
let mutable timeDelta = 0.008f
let moveBalls t =
let active' = active |> List.map (moveBall (theFunction t) timeDelta)
active <- active'
renderer.DrawScene.Add(fun t -> moveBalls t)
renderer.DrawScene.Add(fun t -> drawBalls t)
let bowl t x y =
let f phi u = ((1.0f + cos(2.0f * pi * u + phi )) / 2.0f)
f t x * f 0.0f y + 1.0f
range <- (0.0f,2.0f)
ff <- (fun t -> bowl 0.0f)
addBall (Ball (Vector3(0.1f,0.1f,0.1f),
Vector3(0.6f,0.5f,0.0f)))
Async.Start
(async { for i in 0 .. 6 do
do addBall (Ball (Vector3(0.1f,0.1f,0.1f),
Vector3(0.6f,0.5f,0.0f)))
do! Async.Sleep(100) })
let mutable rate = 0.25f
ff <- (fun t x y -> bowl (rate * t) x y)
rate <- 1.0f
rate <- 2.0f
ff <- ripple
range <- (0.0f,1.0f)
mesh <- BaseMesh.Grid (30,30)
#if COMPILED
[<stathread>]
do Application.Run(form)
do Application.Exit()
#endif
Mathematically, a surface draws a function on a surface for each and Y coordinate in a region of interest. For each X and Y value, a simple surface can have at most one value. Typically, you can define a simple surface by the Y-coordinates of points above a rectangular grid in the X-Z plane. The surface is formed by joining adjacent points using straight lines. So, ensure that the DirectX SDK is installed. After that, download the source file zip file, unzip and extract the files into a newly made folder in the Projects directory of your Visual Studio 2010 folder. Double-click the solution file to let Visual Studio launch and load these files. Remember to right-click the References tab in the Solution Explorer and add the appropriate DLLs. The referenced information contained in this article comes from the blogs and site of Don Syme, the main researcher for the F# language.
History
- 24th October, 2010: Initial post