I was at my local IGDA users group meeting last night and announced the Boston XNA Developers group. After the presentation, I was talking to some folks interested in the XNA group. One of them asked me this question; "How do I draw a line in XNA?". I was a bit baffled at first, thinking, why would you want to do that? But then it struck me that it really wasn't an easy thing to do. In DirectX there was an API to draw primitives like lines and circles. That doesn't exist in XNA. "Why?", you might ask. The answer is fairly simple. XNA does not provide for a Fixed Function Pipeline, you must have an effect in place ( Vertex and Pixel Shader ) in order to draw.
So to answer the question. First, go ahead and create a new Windows XNA Game, I'll wait here.
Done? Great. Now, lets set up some fields we'll need in the Game1 class. Put the following code right after the declaration of the SpritBatch.
VertexPositionNormalTexture[] line; // Our start and end points.
Matrix world, projection, view; // Transformation Matrices
BasicEffect basicEffect; // Standard Effect to draw with
VertexDeclaration vertexDeclaration; // Our format for the line.
Now in the Initialize() method lets call some helper methods to setup for drawing.
InitializeTransforms();
InitializeBasicEffect();
In IntializeTransforms we will set up the world, projection and view matrices so that our line is drawn in the viewport correctly. Then in InitializeBasicEffect() we use those transforms to properly render our graphics. Here is the code for those two methods.
/// <summary>
/// IntializeTranforms is used to set up the matrix transformations required
/// to draw the Display the scene.
/// </summary>
private void InitializeTransforms()
{ world = Matrix.CreateTranslation( new Vector3( -1.5f, -0.5f, 0.0f ) );
view = Matrix.CreateLookAt(
new Vector3( 0.0f, 0.0f, 7.0f ),
new Vector3( 0.0f, 0.0f, 0.0f ),
Vector3.Up
);
projection = Matrix.CreatePerspectiveFieldOfView(
MathHelper.ToRadians( 45 ),
(float)graphics.GraphicsDevice.Viewport.Width /
(float)graphics.GraphicsDevice.Viewport.Height,
1.0f, 100.0f
);
}
/// <summary>
/// Setup up the required effect so we can see our scene
/// </summary>
private void InitializeBasicEffect()
{ // Not part of the Effect per se, but is required for drawing
vertexDeclaration = new VertexDeclaration(
graphics.GraphicsDevice,
VertexPositionNormalTexture.VertexElements
);
basicEffect = new BasicEffect( graphics.GraphicsDevice, null );
basicEffect.DiffuseColor = new Vector3( 1.0f, 1.0f, 1.0f );
basicEffect.World = world;
basicEffect.View = view;
basicEffect.Projection = projection;
}
Now that we have the requirements set up, lets add the code to draw our line. What we need to do in the Draw Method is tell the GraphicsDevice to use our Vertex Format and using the basicEffect, call DrawLine(), which we will implement in a second. Add the following code to the Draw method.
graphics.GraphicsDevice.VertexDeclaration = vertexDeclaration;
// The effect is a compiled effect created and compiled elsewhere
// in the application.
basicEffect.Begin();
foreach ( EffectPass pass in basicEffect.CurrentTechnique.Passes )
{ pass.Begin();
DrawLine();
pass.End();
}
basicEffect.End();
Let's add in a stub for DrawLine() so that the code will compile.
private void DrawLine()
{ }
Alright, at this point we have the skeleton code needed to draw anything we want. Since what we want to draw is a line, we need to initialize our line. Our line is made up of a Position, Normal and Texture Coordinate. Set the line field to a new instance of VertexPositionNormalTexture array with 2 elements. The first element represents the start point and the second element is the end point. Each element of the array will be set to a new instance of VertexPositionNormalTexture. The constructor for VertexNormalTexture takes three arguments, a Vector3 for position, a Vector3 for normal direction and a Vector2 for texture coordinates. The normal and texture coordinates will be set to Vector3.Forward and Vector2.One which has the vertex facing the camera and the coordinate to 1.
line = new VertexPositionNormalTexture[ 2 ];
line[ 0 ] = new VertexPositionNormalTexture( new Vector3( 0, 0, 0 ),
Vector3.Forward,
Vector2.One
);
line[ 1 ] = new VertexPositionNormalTexture( new Vector3( 0, 1, 0 ),
Vector3.Forward,
Vector2.One
);
Now we need to draw the two points we just defined. In our DrawLine method we'll call GraphicsDevice.DrawUserIndexedPrimitives(). We'll use PrimitiveType.LineList to tell XNA how to draw our vertices. The second argument is our array of vertices. The third and fourth parameters are the starting vertex and the number of vertices, respectively. The next three arguments are the index array, start index and count of indicies.
graphics.GraphicsDevice.DrawUserIndexedPrimitives<VertexPositionNormalTexture>(
PrimitiveType.LineList,
line,
0, // vertex buffer offset to add to each element of the index buffer
2, // number of vertices in pointList
new short[2] { 0, 1 }, // the index buffer 0, // first index element to read
1 // number of primitives to draw
);
Run the application, and you'll have a line!