a simple vector-type in F#

As mentioned here I decided to create my own vector-implementation for 3D vectors. I will start with this here and continually extent this type and it’s helper modules as the need arises for our Raytracer.

For now I just want to begin with the basic declaration and the main-operations on a vector. I will try to give some background information on what a vector is in the mathematical sense and some rules for the operations as well but understand that I cannot give a detailed introduction into this. If you get interested in all the details of this stuff I recommend looking into the Wikipedia-article or searching for some literature on “Linear algebra”.

basic-vector type in 3D

We will only need vectors in common 3D-space (real 3-dim vector-space) and you can imagine a vector (and indeed we will define it to be) as just a 3-tuple of real (float) values. Of course we will associate those values with the x-, y- and z-coordinate in 3D space and there is a “deeper” meaning to this (the tuple is a coordinate-representation for you standard-base being the unit-vectors along your coordinate-axis) but – despite being fun – I think it’s pointless to dig to deep into this here and now.

As we might create a lot of those vectors I will go and implement them as a Struct as this is no big deal and might help performance quite a bit (I will not look too much for performance from the start as I strive for clarity but I try to include the obvious ones):

type Vector3 = struct
    val X : float
    val Y : float
    val Z : float

    new (x,y,z) = { X = x; Y = y; Z = z }

    override v.ToString() = sprintf "[%f, %f, %f]" v.X v.Y v.Z
end

some special vectors

For now we will only need the zero-vector (all coordinates 0.0) and the vectors of length 1 pointing along the coordinate-lines:

    let Create (x,y,z) = new Vector3 (x, y, z)

    let Zero = Create (0.0, 0.0, 0.0)

    let E1 = Create (1.0, 0.0, 0.0)
    let E2 = Create (0.0, 1.0, 0.0)
    let E3 = Create (0.0, 0.0, 1.0)

Their use will be explained more in the next section…

basic operations

There are two very basic operations on a vector-space: addition of two vectors and multiplication of a vector by a scalar (just a simple number). The first one can be imagined by “chaining” vectors (put the second to the end of the first, keeping it’s directions and yielding a new vector starting at the first vectors start point and ending at the chained endpoint – have a look at the Wiki-article for this) but the operation on the coordinates is very simple: just add coordinate-wise. The second is even more simple: just stretch the vector by factor equal to the scalar-value – or multiply every coordinate by it:

    static member (+) (a : Vector3, b : Vector3) = new Vector3 (a.X + b.X, a.Y + b.Y, a.Z + b.Z)
    static member (-) (a : Vector3, b : Vector3) = new Vector3 (a.X - b.X, a.Y - b.Y, a.Z - b.Z)
    static member (*) (s : float,   v : Vector3) = new Vector3 (s * v.X, s * v.Y, s * v.Z)

There are a couple of “laws” those operations hold but instead of stating them (a feat. better left in the wiki-article) I will take a few of them and melt them into unit-test:

    [< Test >]
    member test.``coordinates of zero-vector should be 0.0``() =
        // ARRANGE
        let v = Vector.Zero
        // ACT
        let x,y,z = v.X, v.Y, v.Z
        // ASSERT
        x |> should equal 0.0
        y |> should equal 0.0
        z |> should equal 0.0

    [< Tes t>]
    member test.``coordinates of base-vector E1 should be (1,0,0)``() =
        // ARRANGE
        let v = Vector.E1
        // ACT
        let x,y,z = v.X, v.Y, v.Z
        // ASSERT
        x |> should equal 1.0
        y |> should equal 0.0
        z |> should equal 0.0

    [< Tes t>]
    member test.``coordinates of base-vector E2 should be (0,1,0)``() =
        // ARRANGE
        let v = Vector.E2
        // ACT
        let x,y,z = v.X, v.Y, v.Z
        // ASSERT
        x |> should equal 0.0
        y |> should equal 1.0
        z |> should equal 0.0

    [< Tes t>]
    member test.``coordinates of base-vector E3 should be (0,0,1)``() =
        // ARRANGE
        let v = Vector.E3
        // ACT
        let x,y,z = v.X, v.Y, v.Z
        // ASSERT
        x |> should equal 0.0
        y |> should equal 0.0
        z |> should equal 1.0

    [< Test >]
    member test.``zero-vector should be the neutral element of vector-addition``() =
        // ARRANGE
        let v = Vector.Create (2.0, 3.0, 4.0)
        let zero = Vector.Zero
        // ACT
        let sum1 = v + zero
        let sum2 = zero + v
        // ASSERT
        sum1 |> should equal v
        sum2 |> should equal v

    [< Test >]
    member test.``vector addition operates by adding the coordinates``() =
        // ARRANGE
        let a = Vector.Create (2.0, 3.0, 4.0)
        let b = Vector.Create (1.5, 0.5, 0.0)
        // ACT
        let sum = a + b
        // ASSERT
        sum |> should equal (Vector.Create (3.5, 3.5, 4.0))

    [< Test >]
    member test.``scalar-multiplication operates by scaling the coordinates``() =
        // ARRANGE
        let s = 2.5
        let v = Vector.Create (1.0, 2.0, 3.0)
        // ACT
        let v' = s*v
        // ASSERT
        v' |> should equal (Vector.Create (2.5, 5.0, 7.5))

    [< Test >]
    member test.``scalar-multiplication of a vector by 0.0 yields the zero-vector``() =
        // ARRANGE
        let v = Vector.Create (3.0, 2.25, -7.5)
        // ACT
        let v' = 0.0*v
        // ASSERT
        v' |> should equal Vector.Zero

    [< Test >]
    member test.``linear-combination of the base-vectors should equal it's corresponding coordinate-vector``() =
        // ARRANGE
        let a, b, c = 1.0, 3.5, -7.25
        let v = Vector.Create(a,b,c)
        // ACT
        let v' = a*Vector.E1 + b*Vector.E2 + c*Vector.E3
        // ASSERT
        v' |> should equal v

    [< Tes t>]
    member test.``vector-substraction operates by substracting the coordinates``() =
        // ARRANGE
        let a = Vector.Create (4.0, 2.5, 3.75)
        let b = Vector.Create (4.0, 5.0, 0.75)
        // ACT
        let dif = a-b
        // ASSERT
        dif |> should equal (Vector.Create (0.0, -2.5, 3.0))

    [< Test >]
    member test.``substracting a vector from itself yields the zero-vector``() =
        // ARRANGE
        let v = Vector.Create (4.0, 3.0, -2.75)
        // ACT
        let v' = v-v
        // ASSERT
        v' |> should equal Vector.Zero

    [< Test >]
    member test.``scaling a vector by -1.0 yields the inverse of this vector``() =
        // ARRANGE
        let v = Vector.Create (3.0, 2.25, -7.5)
        // ACT
        let v' = -1.0*v
        let sum = v+v'
        // ASSERT
        sum |> should equal Vector.Zero

    [< Test >]
    member test.``substraction by a vector should equal addition by the inverse of it``() =
        // ARRANGE
        let a = Vector.Create (4.0, 2.5, 3.75)
        let b = Vector.Create (4.0, 5.0, 0.75)
        let dif = a-b
        // ACT
        let dif' = a + (-1.0)*b
        // ASSERT
        dif |> should equal (Vector.Create (0.0, -2.5, 3.0))

length of a vector

We will need the length of a given vector (indeed we often only need the square of this – a value that is even easier to calculate) so we start by implementing this.

A not on Tests

I really like the fluent style of testing you can achieve with F#, NUnit and the small FsUnit-file – so thanks for the great job creating this guys!

I will try to include many tests for this little pet-project. Not only to test (it will be hard to predict the outcome of a given ray-trace without running it first) but to give you some clues on how to use the operations and methods and to demonstrate some mathematical-laws/rules.

So let’s start right now with the Length of a vector. In two dimensions it’s easy to see (using the Pythagorean theorem), that the length of a vector v = (a,b) is just

 \left| v \right| = \sqrt{ a^{2} + b^{2} } .

In 3D it’s not much deeper – you only have to use this theorem two times – by first projecting the vectors to (for example) the X-Z-Plane like this:

VLen3D

First project v to the X-, Z- and Y-axis resulting in vectors a, b and c. Calculate the Length of their sum a+b with Pythagoras and then do the same with v = a+b+c by taking sides a+b and c (Yeah I mixed vectors and their length and yeah I don’t put the little arrow on top of the vectors – I see it like my LA prof – it’s a waste of ink(ok not really) and time).

Doing this (easy task) will get you the result: for v = (a,b,c) the length of v will be

 \left| v \right| = \sqrt{a^{2} + b^{2} + c^{2}}.

In F# this translates into:

    let Len2   (v : Vector3) = v.X*v.X + v.Y*v.Y + v.Z*v.Z
    let Length (v : Vector3) = v |> Len2 |> sqrt

Here I separated the function in two parts, because we will need the Len2 function as well.

To help us keep it tidy I will implement those simple operations in a module called Vector. As we are at it: let’s put those “special” vectors in there too:

module Vector =

    let Create (x,y,z) = new Vector3 (x, y, z)

    let Zero = Create (0.0, 0.0, 0.0)

    let E1 = Create (1.0, 0.0, 0.0)
    let E2 = Create (0.0, 1.0, 0.0)
    let E3 = Create (0.0, 0.0, 1.0)

    let Len2   (v : Vector3) = v.X*v.X + v.Y*v.Y + v.Z*v.Z
    let Length (v : Vector3) = v |> Len2 |> sqrt

Here are some tests for this:

    [< Tes t>]
    member test.``Length of zero-vector should be 0.0``() =
        // ARRANGE
        let v = Vector.Zero
        // ACT
        let len = Vector.Length v
        // ASSERT
        len |> should equal 0.0

    [< Test >]
    member test.``length of base-vector E1 should be 1.0``() =
        // ARRANGE
        let v = Vector.E1
        // ACT
        let len = Vector.Length v
        // ASSERT
        len |> should equal 1.0

    [< Test >]
    member test.``length of base-vector E2 should be 1.0``() =
        // ARRANGE
        let v = Vector.E2
        // ACT
        let len = Vector.Length v
        // ASSERT
        len |> should equal 1.0

    [< Test >]
    member test.``length of base-vector E3 should be 1.0``() =
        // ARRANGE
        let v = Vector.E3
        // ACT
        let len = Vector.Length v
        // ASSERT
        len |> should equal 1.0

    [< Test >]
    member test.``scalar-multiplication of a vector by 2.0 doubles it's length``() =
        // ARRANGE
        let v = Vector.Create (3.0, 0.0, 4.0)
        let len = Vector.Length v
        // ACT
        let v' = 2.0*v
        let len' = Vector.Length v'
        // ASSERT
        len' |> should equal (2.0*len)

That’s it for now – next time we will have a look at the dot-product of two vectors.

Please let me know if you like the way I present those topics here! Is it to easy? To hard? To mixed? Is the math uninteresting or to you want more? Is this plain nonsense? Help me out with some comments here Zwinkerndes Smiley