FunTracer: adding planes to the picture

Today we start adding another primitive to the scene – a plane. This is what we are looking for:

FunTracer Scene with Plane

and here is the code that will finally render this:

module SampleScene =

    let redMaterial = { Color = Colors.Red; Specular = 1.0; Reflection = 0.7 }
    let greenMaterial = { Color = Colors.Green; Specular = 0.5; Reflection = 0.1 }
    let blueMaterial = { Color = Colors.Blue; Specular = 1.0; Reflection = 0.9 }

    let private scene =
        let eye = Vector.Create(0.0, 0.0, -10.0)
        let center = Vector.Zero
        let up = Vector.Create(0.0, 1.0, 0.0)
        let right = Vector.Create(1.0, 0.0, 0.0)
        let vp = new ViewPort (eye, center, up, right, 20.0, 20.0)
        let s = new Scene(vp)

        Sphere.Create (Vector.Create(-5.0, -8.0, 15.0), 4.0, redMaterial)
        |> s.AddObject

        Sphere.Create (Vector.Create(5.0, -8.0, 15.0), 4.0, redMaterial)
        |> s.AddObject

        Sphere.Create (Vector.Create(0.0, -8.0, 7.0), 4.0, redMaterial)
        |> s.AddObject

        Sphere.Create (Vector.Create(0.0, 9.0, 16.0), 7.0, blueMaterial)
        |> s.AddObject

        Plane.Create (Vector.Create(0.0, -14.0, 0.0), Vector.Create(0.0, 1.0, 0.0), greenMaterial)
        |> s.AddObject

        let sun = PointLight.CreateWhite(Vector.Create(1.0, 20.0, -10.0))

        let dir = DirectionalLight.CreateWhite(Vector.Create (0.0, -1.0, 0.1))


But before going into the implementation of the Plane.Create method let’s first discuss the simple math behind this.

Representation of a plane

We are going to use a particular easy and useful representation for this – again using the dot-product and it’s property to indicate perpendicular vectors by being zero when used with those.

Suppose you’ve got a point a you know lying in the plane and you’ve got a normal vector to the plane (there are two lying on the same line and in our case you have to choose well for lighting purposes but for this bit of math it’s indifferent because we will only shoot at zero Zwinkerndes Smiley ).

Now for another point b in the plane we must have  (a-b) \cdot n = 0 and so we can define the plane to be all points having this property (if you have one such point c with  (a-c) \cdot n = 0 then a-c is perpendicular to the normal and as a lies in the plane c = a-(a-c) must do so too.

Remark depending on how you like to define your plane (for example one point on it and two linear independent directions on the plane) you can proof all this very easy using the properties of the dot-product –see Wikipedia on this for a very good intro.

Intersecting a plane with a line

So how can we know if a line hits a plane and where does it hit?

That’s easy now. Remember we represented a line by a point s on it and it’s direction d so the line is  L = \left\{ x = s + td : t \in R \right\} . Now we only have to plug this into the representation of the plane and solve for t:

 0 = (a-x) \cdot n = (a-s) \cdot n-t d \cdot n

so if  d \cdot n \neq 0

 t = \frac{ (a-s) \cdot n}{d \cdot n}

and if  d \cdot n = 0 there is no solution so no hit point.

Rays instead of lines

The only difference for a ray is that we are not interested in negative t’s – indeed we want it to be positive to have some distance from the start-point.

Implementation in F#

this is just a literal translation of the math we discussed so far:

module Plane =

    let Create (a : Point, n : Direction, material : Material) : SceneObj =
        let getPointOnRay (ray : Ray) (t : float) =
            if t |> IsPositive then
                let pos = ray.Start + t*ray.Direction
                { Ray = ray; Distance = t;
                Pos = pos; Normal = n;
                Material = material }
                |> Some
        let intersectRayWithPlane (ray : Ray) : float option =
            let k = ray.Direction <*> n
            match k with
            | NearZero -> None
            | _        -> let l = (a - ray.Start) <*> n
                          Some (l/k)
        let hitTest ray = ray |> intersectRayWithPlane |> Option.bind (getPointOnRay ray)
        { HitTest = hitTest }

Note that we took the normal to define the plane as the normal on that plane – so watch it’s direction if you want lighting and reflection on the plane to work our usual way.