Once upon a time when I started XNA programming (not so long ago actually) I remember being annoyed about the lack of basic 2D primitives. There is a perfectly valid reason why – today’s graphic cards are much better at dealing with 3D operations. Doing 2D stuff with them can be rather less efficient which can be considered a major drawback when your main target is a console with less processing power than what can be found on a high-end personal computer. Stop nagging - all consoles are inferior to high-end PCs like those most proper gamers have (all hail the PC, king of game machines). Microsoft addressed this problem (and many similar ones) by removing the option. No slow 2D operations pal, you got to make do with what works better on our inferior hardware. You can not be trusted to make such decisions after all you stingy ignoramus. Well actually, the option is still there assuming you can program the bloody things yourself. You simply have to waste perfectly good time re-inventing the wheel. I just love wasting time in such little things. It gives me an excuse not to work on my projects.
There are a lot of reasons why simple 2D primitives can be extremely useful, especially during development stages. Primitives such as rectangles can be used to build text areas, edit boxes and many other GUI components (XNA doesn’t provide a GUI either), pixels and lines can be used to troubleshoot collision detection methods, circles to visualise bounding spheres, and so on. The list can be quite extensive. As such I would be very surprised if I was to find out that most XNA developers do not hide a collection of 2D routines in a secret location somewhere in their mother’s basement. Maybe we should ask about it in the next population census – the one we recently did was annoyingly free of questions about programming! Anyway, it all comes down to this. It is better to have a line and not need one, than to need a line and have none. Of course very few finished projects actually use those (giving Microsoft an excuse to drop them). They can easily be faked with more efficient sprites after all – that would be 3D textures with pretty pictures on them, rendered with the flat side always facing the camera (which makes them 2D textures!). That is more efficient because using the graphics card to display a pretty picture of a set of lines is less expensive than doing all those nasty calculations needed to generate those lines 60 or more times a second. Using such a pretty picture (a very very very simple one) turns out to be the best way of implementing all the basic 2D primitives missing from the XNA framework. Consider the following three lines of code:
Texture2D pixel;
pixel = new Texture2D(GraphicsDevice, 1, 1);
pixel.SetData(new[] {Color.White} );
What this does is create the simplest texture (or sprite though I hate to use this term since in my days used to mean something different) possible - a solid white picture of size 1x1. The colour of this can be easily changed through the draw function provided by the SpriteBatch class later, thus giving us the fundamental block of drawing pretty much anything we want from a simple line to Leonardo Da Vinci’s last supper (apparently it was tasty). Placing that texture on a location can be as simple as doing something like that:
sprites.Draw(pixel, new Rectangle(x, y, 1, 1), color);
Where sprites is the SpriteBatch object (used for the few 2D operations XNA does support), pixel is the texture we created previously, x and y is the position followed by the size we want our pretty picture to occupy on the screen (1x1 in this case) and, finally, color is the colour we want our pixel to have. It is spelled wrongly because Americans are terribly bad at this sort of thing (despite their apparent love of spelling competitions). It is easy to see how those not so complicated four lines of code can be used to construct the basis of a simple 2D library. With the ability to draw a pixel we can make any kind of 2D shape we desire. To prove this point I decided to make a triangle! No cheating here. I am NOT going to draw a 3D texture and project it to the screen. I am going to make our 2D triangle in the old fashioned way, like we did in the 80s, using maths and logic and other scary words.
A triangle might strike you as probably not the most useful thing to make – certainly finding a use for a 2D triangle is more challenging than thinking of ways to take advantage of rectangles. Not entirely true. Triangles can be used as building blocks for more complicated structures such as circles and polygons and so on (including rectangles). They are complex enough to require building methods to draw more elementary 2D primitives we can add to a library later too. Rest your eyes upon that vision of simplicity and elegance shown on figure 1.
What we want is to make a particularly sexy triangle like the first one in the figure (triangle 1). To make the outline – white border – we will need some sort of line drawing function. Then there is the little problem of filling in the triangle area. Let us not rush ahead though – one problem at a time. Consider the possible ways of drawing lines first. One way of doing so would be to start at one end of the line and plot points (pixels) using our set pixel code until we reach the other end. The number of intermediate steps we need depends on the length of the line. As it turns out there are quite a few algorithms (recipes) based on this principle that we can possible use. However this is not a tutorial about how to make lines so we are going to cheat! That said, if you want to explore the subject further, the code for one of this recipes can still be found in commends somewhere in the source code provided. Back to the cheating thing then - if we study our entire pixel code (the whole one line of it) carefully we are going to find out we can easily modify it to make rectangles. All we have to do is “stretch” the pixel texture so that it occupies more area on the screen (and add a rectangle method in our useful 2D functions library). A solid rectangle could be drawn like this:
sprites.Draw(pixel, new Rectangle(x, y, width, height), color);
Where the meaning of width and height should be obvious (Alien please don’t ask about it, lol). This is exactly what we need to make vertical lines (tall thin rectangles) and horizontal lines (wide thin rectangles). Drawing a diagonal line is just a matter of making a long enough horizontal or vertical line and rotating it – I went for the horizontal one. If we use Vector2 objects to store our lines start and end points then we can use the Distance method provided by that class to figure out how long our horizontal line has to be. The rotation angle is a bit more tricky – it requires something called maths, though not too much of it. A single math function, atan2 is all we need. Atan2 can be used to figure out the angle of a 2D point around the centre of the axis. Conveniently enough, this is exactly the sort of situation we will end up with if we subtract the starting point from the ending point of the line. Pass the resulting angle as a parameter to the SpriteBatch draw method and we are done.
float size = Vector2.Distance(from, to);
float angle = (float)Math.Atan2(to.Y - from.Y, to.X - from.X);
sprites.Draw(pixel, from, null, color,
angle, Vector2.Zero, new Vector2(size, weight),
SpriteEffects.None, 0);
In the code above we used a form of the Draw method that expect a few parameters we do not need – anything we did not need was filled in with Null values. A full list of the parameters and what their use can be found in MSDN so I will not go out of topic to discuss them. Simply notice that when specifying our horizontal line size we used a weight value to control its thickness. We can now wrap this code in a line function and add it to our library. Drawing three such lines (AB, BC and AC) will produce the outline of our triangle (another problem solved). It is time now to consider the second part of the problem, filling in the triangle (or to be more exact, figuring out a way to draw a solid one).
Let us consider strategies for drawing a solid triangle (no cheating this time). The most obvious one is illustrated by figure 1 triangle 2. The idea there is that you interpolate (find intermediate values) between the points in one side of the triangle and draw lines between them and the opposite corner until done. This will actually work perfectly but it is not very efficient. To start with, the points near the corner we start drawing our lines from will be covered multiple times. Then there is the issue of calculating diagonal lines which does take longer than drawing vertical or horizontal lines. It would make better sense to consider a recipe that only cover each point of the triangle area once using horizontal lines. Those can be drawn more efficiently than vertical lines since the pixel values for a horizontal line are typically held in adjacent memory locations (imagine the display area as a 2D array of pixels). Such a method would work great for triangles where one side is actually horizontal. This brings us to figure 1 triangle 3. What it illustrates is that ANY triangle (ABC) can be split into two smaller triangles (ABD and BDC) with one of their edges been a perfect horizontal line – this is great because this is exactly what we want. It is time to start coding then.
The first step is to actually short the corners of the triangle according to how they relate with the Y axis (assuming out 2D area is defined by a horizontal axis X and a vertical one Y). Now, yours truly is a weird eccentric madman (lets face it, why else would write a tutorial like this one in the year 2011). Because of my special defective brain I have decided to start from the lower corner (thus draw triangle BDC first) and work my way up. There is actually a reason for this – when I first implemented such a primitive using assembly in late 80s I wanted to decrease my loop counter so I would not end up using a compare operation (the decrease assembly instruction does set the zero flag needed to exit the loop) that would take an extra instruction cycle. This is not very relevant now but old habits die hard. I always done this thing that way in the past (first in ASM then in Pascal followed by C and C++ and now I making a draw triangle routine yet again using C#!). In any case, it doesn’t matter why, I am drawing that thing the wrong (or maybe the right) way around so keep that in mind when reading the code.
// Order points by swapping them around
Vector2 tp;
if (p2.Y > p1.Y) { tp = p1; p1 = p2; p2 = tp; }
if (p3.Y > p1.Y) { tp = p1; p1 = p3; p3 = tp; }
if (p3.Y > p2.Y) { tp = p2; p2 = p3; p3 = tp; }
Where p1, p2 and p3 are the three corners of our triangle. Then we need to calculate the number of intermediate points needed for each edge of the triangle. We assume we always want to move exactly one point (pixel) in the Y axis (vertical axis) – as such subtracting the Y values of our corners will give us the number of steps needed.
// Number of steps for each edge
int steps13 = (int)(p1.Y - p3.Y);
int steps12 = (int)(p1.Y - p2.Y);
int steps23 = (int)(p2.Y - p3.Y);
Now remember, in our draw loop we will always be moving 1 point on the Y axis for each step. Therefore we only need to calculate the amount x values will vary for each step. We need those values in order to calculate the coordinates of each intermediate point along the edges of the triangle.
// Displacement value for interpolation
float sx13 = (float)(p1.X - p3.X) / (float)steps13;
float sx12 = (float)(p1.X - p2.X) / (float)steps12;
float sx23 = (float)(p2.X - p3.X) / (float)steps23;
Where sx are out displacement values for each step along edges 1-2, 1-3 and 2-3. Now time to draw our first (lower) triangle. The x values (initially set to be the corner we start from) will hold the intermediate values for each edge. A variable called dx will hold the length each horizontal line needs to be in order to fill the gap between the intermediate edge points.
// Draw LOWER part of triangle
float x13 = p1.X;
float x12 = p1.X;
float dx = 1; // Distance (length) of horizontal line to draw
// Loop one
for (int i = 0; i < steps12; i++)
{
// Decrease by step
x13 -= sx13;
x12 -= sx12;
// Calculate new distance between points of the two edges
dx = x13 - x12;
// Draw horizontal line (rectangle used)
if (dx>0)
FillRectangle((int)x12, (int)p1.Y - i,
(int)Math.Abs(dx), 1, color);
else
FillRectangle((int)x13, (int)p1.Y - i,
(int)Math.Abs(dx), 1, color);
}
Notice that we draw horizontal lines in between the intermediate edge points using a fill rectangle function. Its implementation can be found in the source file provided. This function expects a width parameter which means that we need that ugly if statement in the loop to ensure we will always start drawing from the left most side. We also need to use the absolute mathematical function (Abs) to ensure the width passed to FillRectanngle is positive. This if loop would not had been necessary if we had made a horizontal line functions that would do those adjustments for us. The code would have looked prettier but there wouldn’t have been any performance gains from it. Also notice how the loop counter is used to calculate which Y value to use for our horizontal lines. Convenient isn’t it. Drawing the second half of the triangle requires a second loop which is pretty similar to the first one.
// Draw upper part of triangle
// x13 should still hold a midpoint value from last loop
// so we need to connect this with points of x23
// starting at point 2 (p2)
float x23 = p2.X;
for (int i = 0; i < steps23; i++)
{
// Decrease both by step as we move towards top corner
x13 -= sx13;
x23 -= sx23;
// Length of horizontal line to draw
dx = x13 - x23;
if (dx > 0)
FillRectangle((int)x23, (int)p2.Y - i,
(int)Math.Abs(dx), 1, color);
else
FillRectangle((int)x13, (int)p2.Y - i,
(int)Math.Abs(dx), 1, color);
}
The complete code can be found in the file attached (just remove the txt extension) – keep in mind that I implemented this using C# 2010 and XNA 4.0 and that it has not been tested with previous versions of the framework.
This brings us to the end of this tutorial (first proper one I have ever written). Surprisingly, it turns out that it takes longer to explain things than to do them. In any case hopefully you will find some of the code (even if you have no triangle fetishes – triangles mmm) useful. I also hope that this article has given you some insights on how things were done before the era of graphic accelerators and extensive APIs that offer any functions you can possibly need. Programming has never been easier folks, which means you are missing out. Tackling such problems is lots of fun. What better way of spending a boring Friday afternoon. Why don’t you take this a step further then and see if you can extend this library by adding other things to it such as cycles, spirals, polygons or something similar. No much point doing so but it will be entertaining.