Thursday, December 31, 2009

Indexed drawing

I was getting annoyed with having to cast Math.PI which is double to float32 every time. F#, like C# 3.0 allows you to extend existing types by adding your own functionality. You don't have access to any of the internals, but you can add methods and properties that access the public interface. Here is my extension to System.Math:

module Flow.TypeExtensions
open System

type System.Math with
static member Pi = float32(Math.PI)
static member PiOver2 = Math.Pi/2.0f
static member TwoPi = Math.Pi*2.0f


In this tutorial we are drawing a cube. Since a cube requires more vertices than a single triangle and many of them will be the same, we are going to use an index buffer to minimize the amount of vertices. We still need 4 vertices per face since the normals are unique per face. Still, 4 is better than 6. We create a new class type called Shape which will hold our vertices and indices. The helper function Shape.Box will create our cube by contructing a single face and rotating it to form each of the sides.

type Shape = {
Vertices : array< Vertex >
Indices : array< int32 >
}
with
//-------------------------------------------------------------------------
static member Box =
let vertices =
let normal = Vector3( 0.0f, 0.0f,-1.0f)
let face = [|
Vector3(-1.0f, 1.0f,-1.0f);
Vector3( 1.0f,-1.0f,-1.0f);
Vector3(-1.0f,-1.0f,-1.0f);
Vector3( 1.0f, 1.0f,-1.0f)
|]

let rotatedFace rotation =
let vertices = Vector3.TransformCoordinate(face, ref rotation)
let normal = Vector3.TransformCoordinate(normal, rotation)
Array.map (fun vec -> Vertex(vec, normal)) vertices

let aboutY angle = Matrix.RotationY(angle)
let aboutX angle = Matrix.RotationX(angle)

// Create the sides by rotating the front face 4 times about the Y axis
let rec sides(angle, acc) =
if (angle < Math.TwoPi) then
sides(angle+Math.PiOver2, Array.append acc (rotatedFace(aboutY angle)))
else
acc
// Create the top and bottom by rotating the front face 90 and
// -90 degrees about the X axis
let top, bottom = rotatedFace(aboutX Math.PiOver2), rotatedFace(aboutX -Math.PiOver2)
Array.concat ( seq { yield sides(0.0f, [||])
yield top
yield bottom } )

let indices =
// Create indices by incrementing each element of the first face by
// the base offset of the face. This will ensure the order is consistent
// so all the triangles are front facing.
let nextIndices indices increment =
Array.map (fun x -> x+increment) indices

let frontIndices = [|0;1;2;0;3;1|]
let increments = [|for i in [0..5] do yield i*4|]

Array.collect (nextIndices frontIndices) increments
{ Vertices = vertices; Indices = indices }

Our Geometry object will now have an index buffer.

type Geometry(device, shape) =
...
let indexBufferDesc = new BufferDescription(
BindFlags = BindFlags.IndexBuffer,
SizeInBytes = shape.Indices.Length*sizeof< uint32 >,
Usage = ResourceUsage.Immutable)
let indexBuffer = new SlimDX.Direct3D11.Buffer(
device,
new DataStream(shape.Indices, true, false),
indexBufferDesc)

When preparing this geometry we set the index buffer. When drawing we now have to use DrawIndexed instead of the normal Draw call.

member this.Prepare(transforms) =
...
do deviceContext.InputAssembler.SetIndexBuffer(indexBuffer, Format.R32_UInt, 0)

member this.Draw() =
deviceContext.DrawIndexed(shape.Indices.Length, 0, 0)


That pretty much it for this tutorial. As usual the full source code can be found on Google Code

No comments:

Post a Comment