21/06: 3d transfomations
here is the beginnings of a 3D transformation engine.
I thought that it utilized a few impressive features of f# namely: matrix operations, active patterns, and first class events.
the matrix class is defined in FSharp.PowerPack.dll
you can controll the camera and viewer thusley (mouse movements are relative):
hold down 'z' and left mouse button and move mouse to change camera z position.
hold down 'x' and left mouse button and move mouse to change camera x and y position.
use the same commands as above but with the right mouse button to controll viewer position.
hold down 'r' and left mouse button and move mouse to change camera rotation in x/y-axis.
hold down 'r' and right mouse button and move mouse to change camera rotation in z-axis.
for display, it uses the SdlSimple library i wrote and which you can find a link to download here.
#light
//active pattern to catch return values from a matrix transformation:
let (|Matrix|) (m : matrix) =
let getrows column = [0..m.NumCols - 1] |> List.map (fun row -> m.[column, row])
[0..m.NumRows - 1] |> List.map (fun col -> getrows col)
let mutable (viewerx, viewery, viewerz) = (25.0, 25.0, -150.0) //viewer location
let mutable (rotationx, rotationy, rotationz) = (0.0, 0.0, 0.0) //cammera rotation
let mutable (camerax, cameray, cameraz) = (0.0, 0.0, -100.0) //cammera location
//transform a 3d point into 2d space:
let transform (pointx, pointy, pointz) =
let (Matrix [[dx];
[dy];
[dz]])
=
matrix [[1.0;0.0;0.0];
[0.0;cos -rotationx;sin -rotationx];
[0.0;-sin -rotationx;cos -rotationx]]
*
matrix [[cos -rotationy;0.0;-sin -rotationy];
[0.0;1.0;0.0];
[sin -rotationy;0.0;cos -rotationy]]
*
matrix [[cos -rotationz;sin -rotationz;0.0];
[-sin -rotationz;cos -rotationz;0.0];
[0.0;0.0;1.0]]
*
(
matrix [[pointx];
[pointy];
[pointz]]
-
matrix [[camerax];
[cameray];
[cameraz]]
)
let (Matrix [[fx];
[fy];
[fz];
[fw]])
=
matrix [[1.0;0.0;0.0;-viewerx];
[0.0;1.0;0.0;-viewery];
[0.0;0.0;1.0;0.0];
[0.0;0.0;1.0 / viewerz;0.0]]
*
matrix [[dx];
[dy];
[dz];
[1.0]]
(fx / fw |> int, fy / fw |> int)
type shape = { vectors : (float * float * float) list; lines : (int * int) list }
let cube = {
vectors =
[0.0, 0.0, 0.0;
0.0, 50.0, 0.0;
50.0, 50.0, 0.0;
50.0, 0.0, 0.0;
0.0, 0.0, 50.0;
0.0, 50.0, 50.0;
50.0, 50.0, 50.0;
50.0, 0.0, 50.0];
lines = [0,1; 1,2; 2,3; 3,0; 4,5; 5,6; 6,7; 7,4; 0,4; 1,5; 2,6; 3,7]
}
let mutable offsetcube = cube
open System
open Seq
open SdlSimple
SdlSimpleSetup 500 550 16 false
cls "fps"
//debug information:
draw {
writey "viewer:" (400, 400)
writey "x" (400, 410); writey viewerx (420, 410)
writey "y" (400, 420); writey viewery (420, 420)
writey "z" (400, 430); writey viewerz (420, 430)
writey "camera:" (400, 450)
writey "x" (400, 460); writey camerax (420, 460)
writey "y" (400, 470); writey cameray (420, 470)
writey "z" (400, 480); writey cameraz (420, 480)
writey "rotation:" (400, 500)
writey "x" (400, 510); writey rotationx (420, 510)
writey "y" (400, 520); writey rotationy (420, 520)
writey "z" (400, 530); writey rotationz (420, 530)
}
//draw cube:
draw {
let redpixel (x, y) = pixel (colour 255 0 0) (x + 250, y + 275)
let whiteline (x1, y1) (x2, y2) = line (colour 255 255 255) (x1 + 250, y1 + 275) (x2 + 250, y2 + 275)
let points = offsetcube.vectors |> map transform |> Seq.to_array
offsetcube.lines |> Seq.iter (fun (l,r) -> whiteline points.[l] points.[r] |> ignore)
points |> Seq.iter redpixel
}
open EventExtensions
//camera and viewer movements:
Event.mousemove |> Event.pairwise |> Event.state Event.leftdown Event.leftup |> Event.stateofkey 120
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
camerax <- camerax + float(x2 - x1); cameray <- cameray + float(y2 - y1)
)
Event.mousemove |> Event.pairwise |> Event.state Event.leftdown Event.leftup |> Event.stateofkey 122
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
cameraz <- cameraz + float(y2 - y1) / 2.0
)
Event.mousemove |> Event.pairwise |> Event.state Event.rightdown Event.rightup |> Event.stateofkey 120
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
viewerx <- viewerx + float(x2 - x1); viewery <- viewery + float(y2 - y1)
)
Event.mousemove |> Event.pairwise |> Event.state Event.rightdown Event.rightup |> Event.stateofkey 122
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
viewerz <- viewerz - float(y2 - y1) / 2.0
)
Event.mousemove |> Event.pairwise |> Event.state Event.leftdown Event.leftup |> Event.stateofkey 114
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
rotationy <- rotationy - (float(x2 - x1) * 0.01); rotationx <- rotationx + (float(y2 - y1) * 0.01)
)
Event.mousemove |> Event.pairwise |> Event.state Event.rightdown Event.rightup |> Event.stateofkey 114
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
rotationz <- rotationz + (float(y2 - y1) * 0.02)
)
Console.ReadLine() |> ignore
this program uses some extensions to the Event class (EventExtensions.fs) that i have made.
these include refining of SdlSimple events and the 'state' event combinator.
#light
open SdlSimple
open System
module Event =
/// returns a new event that fires when its state is true.
/// the returned event's state is managed by the 'a and 'b argument events that, when fired, set the state to true and false respectivly.
let state (start : IEvent<'a>) (stop : IEvent<'b>) (e : IEvent<'T>) =
let set = ref false
start.Add (fun e -> set := true)
stop.Add (fun e -> set := false)
{new IEvent<'T> with
member this.Add(f) = e.Add (fun x -> if !set then f x)
//this is not a proper implementation for AddHandler and RemoveHandler:
member this.AddHandler(h) = ()
member this.RemoveHandler(h) = ()
}
/// returns an IEvent that fires when the specified key is pressed.
let keypress key = events |> Event.filter ((=) (KeyDown key)) |> Event.map (fun (KeyDown key) -> ())
/// returns an IEvent that fires when the specified key is released.
let keyrelease key = events |> Event.filter ((=) (KeyUp key)) |> Event.map (fun (KeyUp key) -> ())
/// return an Event.state function whos state is altered by the press and release of the argument 'key'.
let stateofkey key = state (keypress key) (keyrelease key)
/// returns an IEvent that fires when the mouse is moved. returns mouse co-ordinates:
let mousemove =
events
|> Event.filter (function MouseMove(x, y) -> true | _ -> false)
|> Event.map (fun (MouseMove(x, y)) -> x, y)
/// fires on a left mouse button down event:
let leftdown = events |> Event.filter ((=) (MouseDown(1)))
/// fires on a left mouse button up event:
let leftup = events |> Event.filter ((=) (MouseUp(1)))
/// fires on a right mouse button down event:
let rightdown = events |> Event.filter ((=) (MouseDown(3)))
/// fires on a right mouse button up event:
let rightup = events |> Event.filter ((=) (MouseUp(3)))
I thought that it utilized a few impressive features of f# namely: matrix operations, active patterns, and first class events.
the matrix class is defined in FSharp.PowerPack.dll
you can controll the camera and viewer thusley (mouse movements are relative):
hold down 'z' and left mouse button and move mouse to change camera z position.
hold down 'x' and left mouse button and move mouse to change camera x and y position.
use the same commands as above but with the right mouse button to controll viewer position.
hold down 'r' and left mouse button and move mouse to change camera rotation in x/y-axis.
hold down 'r' and right mouse button and move mouse to change camera rotation in z-axis.
for display, it uses the SdlSimple library i wrote and which you can find a link to download here.
#light
//active pattern to catch return values from a matrix transformation:
let (|Matrix|) (m : matrix) =
let getrows column = [0..m.NumCols - 1] |> List.map (fun row -> m.[column, row])
[0..m.NumRows - 1] |> List.map (fun col -> getrows col)
let mutable (viewerx, viewery, viewerz) = (25.0, 25.0, -150.0) //viewer location
let mutable (rotationx, rotationy, rotationz) = (0.0, 0.0, 0.0) //cammera rotation
let mutable (camerax, cameray, cameraz) = (0.0, 0.0, -100.0) //cammera location
//transform a 3d point into 2d space:
let transform (pointx, pointy, pointz) =
let (Matrix [[dx];
[dy];
[dz]])
=
matrix [[1.0;0.0;0.0];
[0.0;cos -rotationx;sin -rotationx];
[0.0;-sin -rotationx;cos -rotationx]]
*
matrix [[cos -rotationy;0.0;-sin -rotationy];
[0.0;1.0;0.0];
[sin -rotationy;0.0;cos -rotationy]]
*
matrix [[cos -rotationz;sin -rotationz;0.0];
[-sin -rotationz;cos -rotationz;0.0];
[0.0;0.0;1.0]]
*
(
matrix [[pointx];
[pointy];
[pointz]]
-
matrix [[camerax];
[cameray];
[cameraz]]
)
let (Matrix [[fx];
[fy];
[fz];
[fw]])
=
matrix [[1.0;0.0;0.0;-viewerx];
[0.0;1.0;0.0;-viewery];
[0.0;0.0;1.0;0.0];
[0.0;0.0;1.0 / viewerz;0.0]]
*
matrix [[dx];
[dy];
[dz];
[1.0]]
(fx / fw |> int, fy / fw |> int)
type shape = { vectors : (float * float * float) list; lines : (int * int) list }
let cube = {
vectors =
[0.0, 0.0, 0.0;
0.0, 50.0, 0.0;
50.0, 50.0, 0.0;
50.0, 0.0, 0.0;
0.0, 0.0, 50.0;
0.0, 50.0, 50.0;
50.0, 50.0, 50.0;
50.0, 0.0, 50.0];
lines = [0,1; 1,2; 2,3; 3,0; 4,5; 5,6; 6,7; 7,4; 0,4; 1,5; 2,6; 3,7]
}
let mutable offsetcube = cube
open System
open Seq
open SdlSimple
SdlSimpleSetup 500 550 16 false
cls "fps"
//debug information:
draw {
writey "viewer:" (400, 400)
writey "x" (400, 410); writey viewerx (420, 410)
writey "y" (400, 420); writey viewery (420, 420)
writey "z" (400, 430); writey viewerz (420, 430)
writey "camera:" (400, 450)
writey "x" (400, 460); writey camerax (420, 460)
writey "y" (400, 470); writey cameray (420, 470)
writey "z" (400, 480); writey cameraz (420, 480)
writey "rotation:" (400, 500)
writey "x" (400, 510); writey rotationx (420, 510)
writey "y" (400, 520); writey rotationy (420, 520)
writey "z" (400, 530); writey rotationz (420, 530)
}
//draw cube:
draw {
let redpixel (x, y) = pixel (colour 255 0 0) (x + 250, y + 275)
let whiteline (x1, y1) (x2, y2) = line (colour 255 255 255) (x1 + 250, y1 + 275) (x2 + 250, y2 + 275)
let points = offsetcube.vectors |> map transform |> Seq.to_array
offsetcube.lines |> Seq.iter (fun (l,r) -> whiteline points.[l] points.[r] |> ignore)
points |> Seq.iter redpixel
}
open EventExtensions
//camera and viewer movements:
Event.mousemove |> Event.pairwise |> Event.state Event.leftdown Event.leftup |> Event.stateofkey 120
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
camerax <- camerax + float(x2 - x1); cameray <- cameray + float(y2 - y1)
)
Event.mousemove |> Event.pairwise |> Event.state Event.leftdown Event.leftup |> Event.stateofkey 122
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
cameraz <- cameraz + float(y2 - y1) / 2.0
)
Event.mousemove |> Event.pairwise |> Event.state Event.rightdown Event.rightup |> Event.stateofkey 120
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
viewerx <- viewerx + float(x2 - x1); viewery <- viewery + float(y2 - y1)
)
Event.mousemove |> Event.pairwise |> Event.state Event.rightdown Event.rightup |> Event.stateofkey 122
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
viewerz <- viewerz - float(y2 - y1) / 2.0
)
Event.mousemove |> Event.pairwise |> Event.state Event.leftdown Event.leftup |> Event.stateofkey 114
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
rotationy <- rotationy - (float(x2 - x1) * 0.01); rotationx <- rotationx + (float(y2 - y1) * 0.01)
)
Event.mousemove |> Event.pairwise |> Event.state Event.rightdown Event.rightup |> Event.stateofkey 114
|> Event.listen (fun ((x1, y1), (x2, y2)) ->
rotationz <- rotationz + (float(y2 - y1) * 0.02)
)
Console.ReadLine() |> ignore
EventExtensions.fs
this program uses some extensions to the Event class (EventExtensions.fs) that i have made.
these include refining of SdlSimple events and the 'state' event combinator.
#light
open SdlSimple
open System
module Event =
/// returns a new event that fires when its state is true.
/// the returned event's state is managed by the 'a and 'b argument events that, when fired, set the state to true and false respectivly.
let state (start : IEvent<'a>) (stop : IEvent<'b>) (e : IEvent<'T>) =
let set = ref false
start.Add (fun e -> set := true)
stop.Add (fun e -> set := false)
{new IEvent<'T> with
member this.Add(f) = e.Add (fun x -> if !set then f x)
//this is not a proper implementation for AddHandler and RemoveHandler:
member this.AddHandler(h) = ()
member this.RemoveHandler(h) = ()
}
/// returns an IEvent that fires when the specified key is pressed.
let keypress key = events |> Event.filter ((=) (KeyDown key)) |> Event.map (fun (KeyDown key) -> ())
/// returns an IEvent that fires when the specified key is released.
let keyrelease key = events |> Event.filter ((=) (KeyUp key)) |> Event.map (fun (KeyUp key) -> ())
/// return an Event.state function whos state is altered by the press and release of the argument 'key'.
let stateofkey key = state (keypress key) (keyrelease key)
/// returns an IEvent that fires when the mouse is moved. returns mouse co-ordinates:
let mousemove =
events
|> Event.filter (function MouseMove(x, y) -> true | _ -> false)
|> Event.map (fun (MouseMove(x, y)) -> x, y)
/// fires on a left mouse button down event:
let leftdown = events |> Event.filter ((=) (MouseDown(1)))
/// fires on a left mouse button up event:
let leftup = events |> Event.filter ((=) (MouseUp(1)))
/// fires on a right mouse button down event:
let rightdown = events |> Event.filter ((=) (MouseDown(3)))
/// fires on a right mouse button up event:
let rightup = events |> Event.filter ((=) (MouseUp(3)))