Click here to Skip to main content
15,879,095 members
Articles / Multimedia / DirectX

Managed DirectX via F#

Rate me:
Please Sign up or sign in to vote.
4.80/5 (4 votes)
24 Oct 2010CPOL4 min read 41.2K   483   19   2
An article that illustrates referencing DirectX Libraries in an F# application

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.

Capture.JPG

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:

F#
#load @"BindAsLegacyV2Runtime.fs"
// adjust these as needed for your latest installed version of ManagedDirectX
#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))

/// Z-range
let mutable range = (0.0f,1.0f)

/// XY-mesh
let mutable mesh = BaseMesh.Grid(20,20)

//mesh

// Scale w.r.t. range ... 
let scalef (min,max) (z:float32) = (z-min) / (max-min) 

// Get the function and scale it 
let theFunction t x y = ff t x y |> scalef range

renderer.DrawScene.Add(fun t -> renderer.DrawSurface mesh (theFunction t))

//----------------------------------------------------------------------------
// PART 2 - change the function

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)

// A ball is a pair of position/velocity vectors
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              // acceleration in plane 
    let velocity = planeProject nHat velocity             // velocity     in plane 
    
    // Compute the new position
    let position = position + Vector3.Scale(velocity,timeDelta)  // iterate 
    let velocity = velocity + Vector3.Scale(acc     ,timeDelta)  // iterate 

    // Handle the bounce!
    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)              // bounce X edges 
    let py,vy = bounce (position.Y,velocity.Y)              // bounce Y edges 
    let position = surfacePoint f px py                     // keep to surface 
    let velocity = Vector3 (vx,vy,velocity.Z) 
    let velocity = planeProject nHat velocity               // velocity in plane     

    Ball (position,velocity)

let drawBall t (Ball (p,v)) =
    let n    = surfaceNormal (theFunction t) p.X p.Y 
    // position XY-projection 
    let p0   = Vector3(p.X,p.Y,0.0f) 
    // unit velocity XY-projection 
    let pV   = Vector3(v.X,v.Y,0.0f)                  
    // and it's XY-perpendicular 
    let pVxZ = Vector3.Cross(pV,Z1)                         
    // vertical line 
    renderer.DrawLines (Array.map (Vertex.Colored Color.Gray) [| p0;p |])
    // velocity arrow on floor 
    renderer.DrawPlaneArrow Z1 p0 pV      
    // normal arrow at point     
    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))
      
    // Now draw the mesh
    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'

//timeDelta <- 0.014f
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)

// Second, add a ball 
addBall (Ball (Vector3(0.1f,0.1f,0.1f),
               Vector3(0.6f,0.5f,0.0f)))

// Add a ball train. 

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)  })

// Now move the floor!

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

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer Monroe Community
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralGood work, but ... Pin
Dennis Dykstra28-Oct-10 4:35
Dennis Dykstra28-Oct-10 4:35 
GeneralMy vote of 5 Pin
GPUToaster™25-Oct-10 1:59
GPUToaster™25-Oct-10 1:59 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.