#light
#r "FSharp.Core"
#r "PresentationCore"
#r "PresentationFramework"
#r "WindowsBase"
#r "FSharp.PowerPack"
#r "System.Xaml"
open System
open System.Collections.Generic
open System.IO
open System.Net
open System.Text
open System.Windows
open System.Windows.Shapes
open System.Windows.Controls
open System.Windows.Markup
open System.Windows.Input
open System.Windows.Media
open System.Windows.Media.Media3D
open System.Windows.Media.Imaging
open System.Windows.Threading
open System.Xml
open System.Diagnostics
open System.Threading
open System.Xaml
(* Helper functions *)
let parseXAML (xaml : string) =
use ms = new MemoryStream(Encoding.ASCII.GetBytes(xaml))
ms.Position <- int64 0
XamlReader.Load(ms)
let getUrlAsTxt (url:string) =
use sr = new StreamReader(WebRequest.Create(url).GetResponse().GetResponseStream())
sr.ReadToEnd()
let square() =
let sqrXaml ="
"
parseXAML(sqrXaml) :?> MeshGeometry3D
let cube() =
let cubeXAML = "
"
parseXAML(cubeXAML) :?> MeshGeometry3D
let windowXaml = "
"
// create our window
let window = parseXAML(windowXaml) :?> Window
let viewport = window.FindName("ViewPort") :?> Viewport3D
do
let backPanel = window.FindName("BackPanel") :?> Canvas
let frontPanel = window.FindName("FrontPanel") :?> Canvas
// Add camera to viewport
let camera = PerspectiveCamera(Point3D(0.,0.,0.), Vector3D(0., 0., 1.), Vector3D(0., 1., 0.), 45.)
viewport.Camera <- camera
// Create the transforms
let zoom = TranslateTransform3D()
let tran = TranslateTransform3D()
let rotx = AxisAngleRotation3D(Vector3D(1.,0.,0.),0.)
let roty = AxisAngleRotation3D(Vector3D(0.,1.,0.),0.)
let rotz = AxisAngleRotation3D(Vector3D(0.,0.,1.),0.)
zoom.OffsetZ <- -10.
roty.Angle <- 180.
// Add the transform to the camera
let group = Transform3DGroup()
group.Children.Add(zoom)
group.Children.Add(RotateTransform3D(rotz))
group.Children.Add(RotateTransform3D(rotx))
group.Children.Add(RotateTransform3D(roty))
group.Children.Add(tran)
camera.Transform <- group
let addFront c = frontPanel.Children.Add(c)
let removeFront c = frontPanel.Children.Remove(c)
let getAspectRatio() = viewport.ActualWidth / viewport.ActualHeight
let getProjMatrix (camera:PerspectiveCamera) aspectRatio =
let degToRad deg = deg * (Math.PI / 180.0)
let hFov = degToRad camera.FieldOfView
let zn = camera.NearPlaneDistance
let zf = camera.FarPlaneDistance
let xScale = 1.0 / tan(hFov / 2.0)
let yScale = aspectRatio * xScale
let m33 =
if zf = Double.PositiveInfinity then
-1.
else
zf / (zn - zf)
let m43 = zn * m33
Matrix3D(
xScale, 0., 0., 0.,
0., yScale, 0., 0.,
0., 0., m33, -1.,
0., 0., m43, 0.)
let getViewMatrix (camera:PerspectiveCamera) =
let zAxis = -camera.LookDirection
zAxis.Normalize()
let xAxis = Vector3D.CrossProduct(camera.UpDirection, zAxis)
xAxis.Normalize()
let yAxis = Vector3D.CrossProduct(zAxis,xAxis)
let position = Vector3D(X=camera.Position.X,Y=camera.Position.Y,Z=camera.Position.Z)
let offsetX = -Vector3D.DotProduct(xAxis,position)
let offsetY = -Vector3D.DotProduct(yAxis,position)
let offsetZ = -Vector3D.DotProduct(zAxis,position)
Matrix3D(
xAxis.X, yAxis.X, zAxis.X, 0.,
xAxis.Y, yAxis.Y, zAxis.Y, 0.,
xAxis.Z, yAxis.Z, zAxis.Z, 0.,
offsetX, offsetY, offsetZ, 1.)
// Camrea Settings
let first = ref true // set to true when viewport3D focus is lost
let lastMouseMovePos = ref (Point())
let lastMouseDownPos = ref (Point())
let mouse_move = fun (e:Input.MouseEventArgs) ->
let p = e.GetPosition(viewport)
if !first then
lastMouseMovePos := p
first := false
let d = !lastMouseMovePos - p
if e.RightButton = Input.MouseButtonState.Pressed then
if e.LeftButton = Input.MouseButtonState.Pressed then
// zoom
zoom.OffsetZ <- zoom.OffsetZ + zoom.OffsetZ * 10. * d.Y / viewport.ActualHeight
else
// rotation
rotx.Angle <- rotx.Angle + (d.Y / viewport.ActualHeight) * 180. // z is pitch
roty.Angle <- roty.Angle + (d.X / viewport.ActualWidth) * 180.
lastMouseMovePos := p
backPanel.MouseMove.Add(mouse_move)
viewport.MouseMove.Add(mouse_move)
backPanel.MouseWheel.Add(fun e -> if e.Delta < 0 then zoom.OffsetZ <- zoom.OffsetZ * 1.1 else zoom.OffsetZ <- zoom.OffsetZ / 1.1)
viewport.MouseWheel.Add(fun e -> if e.Delta < 0 then zoom.OffsetZ <- zoom.OffsetZ * 1.1 else zoom.OffsetZ <- zoom.OffsetZ / 1.1)
backPanel.MouseUp.Add((fun _ -> first := true))
viewport.MouseUp.Add((fun _ -> first := true))
backPanel.MouseLeave.Add(fun _ -> first := true)
viewport.MouseLeave.Add(fun _ -> first := true)
let key_down = fun (e:KeyEventArgs) ->
if Keyboard.IsKeyDown(Key.A) then tran.OffsetX <- tran.OffsetX - 1.
if Keyboard.IsKeyDown(Key.D) then tran.OffsetX <- tran.OffsetX + 1.
if Keyboard.IsKeyDown(Key.W) then tran.OffsetZ <- tran.OffsetZ - 1.
if Keyboard.IsKeyDown(Key.S) then tran.OffsetZ <- tran.OffsetZ + 1.
if Keyboard.IsKeyDown(Key.Space) then tran.OffsetY <- tran.OffsetY + 1.
if Keyboard.IsKeyDown(Key.LeftCtrl) then tran.OffsetY <- tran.OffsetY - 1.
e.Handled <- false
window.KeyDown.Add(key_down)
/// Function to add the model to the viewport
let add mv3d = viewport.Children.Add(mv3d); mv3d
/// Function to remove the model from the viewport
let remove mv3d = viewport.Children.Remove(mv3d) |> ignore
let addRange rs = Array.map (fun m -> m |> add) rs
let removeRange rs = Array.map (fun m -> m |> remove) rs
window.Topmost <- true
window.Show()
module Math3D =
/// Multiply a 3d vector by a 4x4 matrix
let multMV (m:matrix) (v:vector) =
let tmp = (m * vector [v.[0]; v.[1]; v.[2]; 1.])
vector [tmp.[0]; tmp.[1]; tmp.[2]]
let cross (v1 : vector) (v2 : vector) =
vector[v1.[1]*v2.[2]-v1.[2]*v2.[1];v1.[2]*v2.[0]-v1.[0]*v2.[2];v1.[0]*v2.[1]-v1.[1]*v2.[0]]
let dot (v1:vector) (v2:vector) = v1.Transpose * v2
let length (v:vector) = sqrt (dot v v)
let angle_between v1 v2 = acos ((dot v1 v2) / ((v1 |> length) * (v2 |> length)))
let unit =
matrix [[ 1.; 0.; 0.; 0.];
[ 0.; 1.; 0.; 0.];
[ 0.; 0.; 1.; 0.];
[ 0.; 0.; 0.; 1.]]
let scale x y z =
matrix [[ x; 0.; 0.; 0.];
[ 0.; y; 0.; 0.];
[ 0.; 0.; z; 0.];
[ 0.; 0.; 0.; 1.]]
let translate x y z =
matrix [[ 1.; 0.; 0.; x];
[ 0.; 1.; 0.; y];
[ 0.; 0.; 1.; z];
[ 0.; 0.; 0.; 1.]]
let rotateX t =
let c = cos t
let s = sin t
matrix [[ 1.; 0.; 0.; 0.];
[ 0.; c; s; 0.];
[ 0.; -s; c; 0.];
[ 0.; 0.; 0.; 1.]]
let rotateY t =
let c = cos t
let s = sin t
matrix [[ c; 0.; s; 0.];
[ 0.; 1.; 0.; 0.];
[ -s; 0.; c; 0.];
[ 0.; 0.; 0.; 1.]]
let rotateZ t =
let c = cos t
let s = sin t
matrix [[ c; s; 0.; 0.];
[ -s; c; 0.; 0.];
[ 0.; 0.; 1.; 0.];
[ 0.; 0.; 0.; 1.]]
let rotateXYZ x y z =
(rotateX x) * (rotateY y) * (rotateZ z)
let rotateAxisAngle (v:vector) a =
let c = cos a
let s = sin a
let x = v.[0]
let y = v.[1]
let z = v.[2]
matrix [[ x*x + (1.-x*x)*c; x*y*(1.-c) - z*s; x*z*(1.-c) + z*s; 0.];
[ x*y*(1.-c) + z*s; y*y + (1.-y*y)*c; y*z*(1.-c) - x*s; 0.];
[ x*z*(1.-c) - y*s; y*z*(1.-c) + x*s; z*z + (1.-z*z)*c; 0.];
[ 0.; 0.; 0.; 1.]]
/// rotation of a (-1,0,0) vector to for a vector at point 1 to point to point 2
let rotateP1toP2 p1 p2 =
let v = p1 - p2
rotateAxisAngle (cross v (vector [-1.; 0.; 0.])) (angle_between v (vector [1.; 0.; 0.]))
/// Get a Matrix3D object from a matrix for use with Point3D and Vector3D
let Matrix3D (m:matrix) =
new Matrix3D( m.[0,0], m.[1,0], m.[2,0], m.[3,0],
m.[0,1], m.[1,1], m.[2,1], m.[3,1],
m.[0,2], m.[1,2], m.[2,2], m.[3,2],
m.[0,3], m.[1,3], m.[2,3], m.[3,3])
let applyMatrix matrix (mv3D:Visual3D) = mv3D.Transform <- MatrixTransform3D(matrix); mv3D
let addMatrix matrix (mv3D:Visual3D) =
match mv3D.Transform with
| :? MatrixTransform3D as mx -> mv3D.Transform <- MatrixTransform3D(mx.Matrix * matrix)
| _ -> mv3D.Transform <- MatrixTransform3D(matrix)
mv3D
let translate x y z (mv3D:Visual3D) = addMatrix (Math3D.Matrix3D(Math3D.translate x y z)) mv3D
let scale x y z (mv3D:Visual3D) = addMatrix (Math3D.Matrix3D(Math3D.scale x y z)) mv3D
let rotate x y z (mv3D:Visual3D) = addMatrix (Math3D.Matrix3D(Math3D.rotateXYZ x y z)) mv3D
let reset (mv3D:Visual3D) = applyMatrix (Math3D.Matrix3D(Math3D.unit)) mv3D
let imageModel url model = ModelVisual3D(Content = GeometryModel3D(Geometry = (model), Material = (DiffuseMaterial(ImageBrush(BitmapImage(Uri(url)))))))
let brushModel brush model = ModelVisual3D(Content = GeometryModel3D(Geometry = (model), BackMaterial = (DiffuseMaterial(brush)), Material = (DiffuseMaterial(Brushes.Goldenrod))))
let terrain =
square()
|> imageModel @"http://www.mattssoftwareblog.com/software/3DWPF/skyPic.jpeg"
|> scale 200. 200. 200.
|> rotate (Math.PI / 2.0) 0. 0.
|> translate 1. -1. 1.
|> add
let tank1 =
@"http://www.mattssoftwareblog.com/software/3DWPF/TankXAML.txt" |> getUrlAsTxt|> parseXAML :?> ModelVisual3D
|> rotate 0. (Math.PI * 0.3) 0.
|> scale 0.1 0.1 0.1
|> translate -50. 1. 5.
|> add
let tank2 =
@"http://www.mattssoftwareblog.com/software/3DWPF/TankXAML.txt" |> getUrlAsTxt |> parseXAML :?> ModelVisual3D
|> scale 0.1 0.1 0.1
|> translate -40. 1. -20.
|> add
let walker1 =
@"http://www.mattssoftwareblog.com/software/3DWPF/WalkerXAML.txt" |> getUrlAsTxt|> parseXAML :?> ModelVisual3D
|> rotate 0. (Math.PI * 1.5) 0.
|> translate 5. -1. 5.
|> add
let walker2 =
@"http://www.mattssoftwareblog.com/software/3DWPF/WalkerXAML.txt" |> getUrlAsTxt |> parseXAML :?> ModelVisual3D
|> rotate 0. (Math.PI * 1.3) 0.
|> translate 0. -1. 10.
|> add
let cube1 =
cube()
|> brushModel Brushes.Goldenrod
|> add
cube1 |> scale 1.2 1.1 1.2
cube1 |> rotate 0. 0.2 0.
cube1 |> translate 1. 1. 0.
cube1 |> reset |> scale 5. 5. 5. |> translate 100. -1. 0.
cube1 |> rotate 0. 0.1 0.