Programming 2D Computer Graphics
In This Chapter
Understanding Screen and Cartesian Coordinates
Defining Vertex and Shape Data Types
Transforming Shapes
Using Matrix Math in Transformations
Building a 2D Graphics Application
Running the Graphics2D Application
Understanding How the Graphics2D Program Works
Exploring the Program Listings
In Brief
As you've learned, Direct3D provides many powerful functions for creating 3D graphics on your computer. These functions hide many programming details that must be dealt with to produce sophisticated graphics. Still, to understand Direct3D, you need to have a little background in standard 3D programming practices. The first step toward that goal is to understand how your computer programs can manipulate simpler 2D images.
This chapter, then, introduces you to the basics of 2D graphics programming, including the formulas needed to transform (move, rotate, scale, and so on) 2D shapes in various ways. Although transforming 2D shapes requires that you know a set of formulas for manipulating the points that define a shape, you'll discover that these formulas are easy to use, even if you don't really understand exactly how they work.
In this chapter, you learn:
About screen and Cartesian coordinates
About using vertices to define 2D shapes
About translating, scaling, and rotating 2D shapes
How to use matrices to transform 2D shapes
Understanding Screen and Cartesian Coordinates
Undoubtedly, you have at least some minimal experience with drawing images on a computer screen under Windows. For example, you probably know that to draw a line in a window, you must call the GDI function MoveToEx() to position the starting point of the line and then call the function LineTo() to draw the line. In a program, those two function calls would look something like this:
MoveToEx(hDC, x, y, 0); LineTo(hDC, x, y);
The arguments for the MoveToEx() function are a handle to a device context (DC), the X,Y coordinates of the point in the window to which you want to move, and a pointer to a POINT structure in which the function should store the current position before moving to the new position. If this last argument is 0, MoveToEx() doesn't supply the current position. The arguments for the LineTo() function are a handle to a DC and the X,Y coordinates of the end of the line.
Of interest here are the X,Y coordinates used for the end points that define the line. Assuming the default mapping mode of MM_TEXT, both sets of coordinates are based on window coordinates, which, like normal screen coordinates, begin in the upper-left corner of the window, with the X coordinate increasing as you move to the right and the Y coordinate increasing as you move down. Figure 3.1 shows this coordinate system.
Figure 3.1 A window's MM_TEXT coordinate system.
Most computer programmers are familiar with the coordinate system shown in Figure 3.1. Unfortunately, most objects in computer graphics are defined using the Cartesian coordinate system, which reverses the Y coordinates so that they increase as you move up from the origin. Also, as shown in Figure 3.2, the Cartesian coordinate system allows negative coordinates. If you remember any of your high school math, you'll recognize Figure 3.2 as the plane on which you graphed equations. In computer graphics, however, you'll use the Cartesian plane as a surface that represents the world in which your graphical objects exist.
Figure 3.2 The Cartesian coordinate system.
You define graphical objects in the Cartesian coordinate system by specifying the coordinates of their vertices, which are the points at which the lines that make up the object connect. For example, a triangle can be defined by three points, as shown in Figure 3.3. The defining points in the triangle are (2,5), (5,2), and (2,2).
Figure 3.3 Defining a triangle in the Cartesian coordinate system.
A problem arises, however, when you try to draw an object defined in the Cartesian coordinate system onscreen. As you can see by Figure 3.4, the figure comes out upside-down due to the reversal of the Y coordinates in the screen's coordinate system as compared with the Cartesian coordinate system. The C++ code that produces the triangle looks like this:
MoveToEx(hDC, 2, 5, 0); LineTo(hDC, 5, 2); LineTo(hDC, 2, 2); LineTo(hDC, 2, 5);
Figure 3.4 Drawing a triangle with no mapping between the Cartesian and screen coordinate systems.
Because of the differences between a screen display and the Cartesian coordinate system, you need a way to translate points from one system to the other. In graphics terms, you must map points in the Cartesian coordinate system to points in the screen coordinate system so that objects you draw onscreen are positioned correctly. Forgetting about negative coordinates for the time being, mapping point (x1,y1) in the Cartesian coordinate system to point (x2,y2) in the screen coordinate system requires the following simple formulas, shown in C++ program code:
x2 = y1; y2 = maxY - y1;
Because the X coordinate is unaffected by the mapping, x2 is simply assigned the value of x1. To reverse the Y coordinate, the original Y coordinate is subtracted from the window's maximum Y coordinate. Of course, for this formula to work, you must know the current size of the window. You can get this value by calling the Windows API function GetClientRect(), which fills a RECT structure with the size of the window's client area. Listing 3.1 draws a triangle in the window, mapping between the Cartesian coordinates and the screen coordinates.
Listing 3.1 Drawing a Triangle
int triangle[6] = {2, 5, 5, 2, 2, 2}; int newX, newY, startX, startY; RECT clientRect; GetClientRect(hWnd, &clientRect); int maxY = clientRect.bottom; for (int x=0; x<3; ++x) { newX = triangle[x*2]; newY = maxY - triangle[x*2+1]; if (x == 0) { MoveToEx(hDC, newX, newY, 0); startX = newX; startY = newY; } else LineTo(hDC, newX, newY); } LineTo(hDC, startX, startY);
(Note that the preceding code segment is not complete and will not run on your computer. Later in this chapter, you develop a complete Windows program that demonstrates the topics discussed in this chapter.)
In the preceding code, the first line defines an array that contains the Cartesian coordinates of the triangle. The variables newX and newY will hold the screen coordinates for a point, and the variables startX and startY will hold the screen coordinates for the first point in the triangle. The RECT structure, clientRect, will hold the size of the window's client area.
After declaring the local variables, the code calls GetClientRect() to fill in the clientRect structure, at which point the structure's bottom member will hold the height of the window's client area. The code assigns this value to the local variable maxY.
A for loop then iterates through the triangle's coordinate array. Inside the loop, the currently indexed X,Y coordinates are mapped from Cartesian coordinates to screen coordinates. The first mapped point is used to set the starting point of the triangle. The code uses subsequent points to draw the lines that make up the triangle. The call to LineTo() outside the loop connects the last point of the triangle to the first point.