Building a 2D Graphics Application
You now have all the information you need to write a 2D graphics program that can draw various shapes as well as translate, scale, and rotate those shapes. In Chapter 2, "Writing a Windows Program," you learned how to write C++ Windows programs that don't rely on the Microsoft Foundation Classes, and for the Direct3D programs you'll develop later in this book, you will use those programming techniques. However, you develop the programs in this chapter and in Chapter 4, "Programming 3D Computer Graphics," using Visual Studio's application wizards and MFC. Perform the steps in the following sections to create a program called Graphics2D that demonstrates the principles you learned in this chapter.
Creating the Basic Project
In the first set of steps that follow, you create the basic project and modify the application's user interface. (The following steps assume that you have experience with using Visual C++ to write Windows applications. If you don't know how to perform any of the following steps, consult your Visual C++ documentation.)
-
Create a new MFC application named Graphics2D, as shown in Figure 3.13.
Figure 3.13 Creating the Graphics2D project.
-
In the MFC Application Wizard dialog box, click the Application Type selection. Then set the Application Type option to Single Document and the Use of MFC option to Use MFC in a Static Library, as shown in Figure 3.14.
Figure 3.14 The Application Type options.
-
Click the User Interface Features selection. Then turn off the Initial Status Bar option, as shown in Figure 3.15.
Figure 3.15 The User Interface Features options.
-
Again in the MFC Application Wizard dialog box, click the Advanced Features selection. Then turn off the Printing and Print Preview and ActiveX Controls options, as shown in Figure 3.16.
Figure 3.16 The Advanced Features options.
-
Click Finish to generate the project's source code files.
Editing the Project's Resources
Next, you modify the application's resources, including menus and dialog boxes. Perform the following steps to get your program's resources ready to go:
Double-click the Graphics2D.rc file in the Solution Explorer to bring up the Resource View pane.
-
Double-click the Menu folder, and then double-click IDR_MAINFRAME to bring up the menu editor (see Figure 3.17).
Figure 3.17 The menu editor.
-
Delete all entries from the File menu except Exit (see Figure 3.18).
Figure 3.18 The new File menu.
-
Delete the Edit and View menus, leaving the File and Help menus, as shown in Figure 3.19.
Figure 3.19 The menu bar after deleting the Edit menu.
-
Add a Transform menu, giving it the commands Translate, Scale, and Rotate (see Figure 3.20).
Figure 3.20 The new Transform menu.
-
Close the menu editor and bring up the dialog box editor (see Figure 3.21) by double-clicking the Dialog folder in the Resource View pane and then double-clicking IDD_ABOUTBOX.
Figure 3.21 The dialog editor.
-
Modify the About dialog box so that it looks like Figure 3.22.
Figure 3.22 The new About dialog box.
-
Close the dialog box editor and double-click Accelerator in the Resource View pane. You'll see the IDR_MAINFRAME accelerator ID (see Figure 3.23). Delete the IDR_MAINFRAME accelerator table from the browser window.
Figure 3.23 The IDR_MAINFRAME accelerator table.
-
Select the Project menu's Add Resource command, and create a new dialog box. The finished dialog box should look like Figure 3.24. (Be sure to give the dialog box the IDD_TRANSFORM ID, and give the edit controls the IDs IDC_XAXIS and IDC_YAXIS.)
Figure 3.24 The Transform dialog box.
-
Double-click the dialog box to bring up the MFC Class Wizard dialog box. Name the Transform dialog box's class CTransformDlg and set the Base Class type to CDialog (see Figure 3.25). Click Finish to create the class.
Figure 3.25 Creating the CTransformDlg class.
-
Switch to the Class View pane and then right-click the CTransformDlg class. Select the Add/Add Variable command from the menu that appears. Use this command to add public variables called m_xAxis and m_yAxis of type double. Associate these variables with the edit controls, as shown in Figure 3.26.
Figure 3.26 Creating a variable for one of the edit controls.
-
Press Ctrl+Shift+S to save all your changes.
Creating Message-Response Functions
In the next set of steps, you add message-response functions for the commands in the Transform menu and for the WM_RBUTTONDOWN Windows message.
In the Class View pane, click on the CGraphics2DView class. In the class's Properties window, click the Events button (the one that looks like a lightning bolt) to display the many events to which the class can respond.
-
Add COMMAND functions for the ID_TRANSFORM_ROTATE, ID_TRANSFORM_SCALE, and ID_TRANSFORM_TRANSLATE commands. Stick with the default names of OnTransformRotate(), OnTransformScale(), and OnTransformTranslate(), as shown in Figure 3.27.
Figure 3.27 Adding response functions for the Transform menu commands.
-
Find the OnTransformRotate() function at the end of the Graphics2DView.cpp file, and add Listing 3.16 to that function, right after the // TODO: Add your command handler code here comment.
Listing 3.16 New Code for OnTransformRotate()
CTransformDlg dlg; dlg.m_xAxis = 0; dlg.m_yAxis = 0; INT_PTR response = dlg.DoModal(); if (response == IDOK) m_rotate = (int) dlg.m_xAxis;
-
Find the OnTransformScale() function at the end of the Graphics2DView.cpp file, and add Listing 3.17 to that function, right after the // TODO: Add your command handler code here comment.
Listing 3.17 New Code for OnTransformScale()
CTransformDlg dlg; dlg.m_xAxis = 1; dlg.m_yAxis = 1; INT_PTR response = dlg.DoModal(); if (response == IDOK) { m_xScale = dlg.m_xAxis; m_yScale = dlg.m_yAxis; }
-
Find the OnTransformTranslate() function at the end of the Graphics2DView.cpp file, and add Listing 3.18 to that function, right after the // TODO: Add your command handler code here comment.
Listing 3.18 New Code for OnTransformTranslate()
CTransformDlg dlg; dlg.m_xAxis = 0; dlg.m_yAxis = 0; INT_PTR response = dlg.DoModal(); if (response == IDOK) { m_xTranslate = dlg.m_xAxis; m_yTranslate = dlg.m_yAxis; }
-
In the class's Properties window, click the Messages button (the one to the right of the lightning bolt) to display the many Windows messages to which the class can respond.
-
Create a response function named OnRButtonDown() for the WM_RBUTTONDOWN message, as shown in Figure 3.28.
Figure 3.28 Adding the OnRButtonDown() function.
-
Add Listing 3.19 to the OnRButtonDown() function, right after the // TODO: Add your message handler code here and/or call default comment.
Listing 3.19 New Code for OnRButtonDown()
MATRIX3X3 m; InitMatrix(m); Translate(m, (int) m_xTranslate, (int) m_yTranslate); Scale(m, m_xScale, m_yScale); Rotate(m, m_rotate); Transform(m_polygon, m); m_rotate = 0; m_xScale = 1; m_yScale = 1; m_xTranslate = 0; m_yTranslate = 0; Invalidate(TRUE);
Finishing the View Class
In the next set of steps, you add the source code that completes the CGraphics2DView class. You also add the required member function and data member declarations to the CGraphics2DView class's header file. Perform the following steps to complete these tasks:
-
Add the following lines near the top of the Graphics2DView.cpp file, right after the #endif compiler directive:
#include <math.h> #include "TransformDlg.h"
-
Add Listing 3.20 to the CGraphics2DView class's constructor, right after the // TODO: add construction code here comment.
Listing 3.20 New Code for CGraphics2DView Constructor
m_polygon.numVerts = 4; m_polygon.vertices = m_vectors; m_vectors[0].x = 0; m_vectors[0].y = 0; m_vectors[0].w = 1; m_vectors[1].x = 100; m_vectors[1].y = 0; m_vectors[1].w = 1; m_vectors[2].x = 100; m_vectors[2].y = 50; m_vectors[2].w = 1; m_vectors[3].x = 0; m_vectors[3].y = 75; m_vectors[3].w = 1; m_rotate = 0; m_xScale = 1; m_yScale = 1; m_xTranslate = 0; m_yTranslate = 0;
-
Add the following line to the CGraphics2DView class's OnDraw() function, right after the // TODO: add draw code for native data here comment:
DrawShape(pDC, m_polygon);
-
Again in OnDraw(), uncomment the pDC parameter.
-
Also in Graphics2DView.cpp, add the functions in Listing 3.21 to the end of the file. Note that because this is an MFC program, Windows API functions such as MoveToEx() and LineTo() have been replaced with their MFC counterparts, but their functionality is the same.
Listing 3.21 New Functions for the CGraphics2DView Class
void CGraphics2DView::InitMatrix(MATRIX3X3& m) { m[0][0]=1; m[0][1]=0; m[0][2]=0; m[1][0]=0; m[1][1]=1; m[1][2]=0; m[2][0]=0; m[2][1]=0; m[2][2]=1; } void CGraphics2DView::CopyMatrix(MATRIX3X3& dst, MATRIX3X3& src) { for (int i=0; i<3; ++i) for (int j=0; j<3; ++j) dst[i][j] = src[i][j]; } void CGraphics2DView::MultMatrix(MATRIX3X3& product, MATRIX3X3& matrix1, MATRIX3X3& matrix2) { for (int x=0; x<3; ++x) for (int y=0; y<3; ++y) { double sum = 0; for (int z=0; z<3; ++z) sum += matrix1[x][z] * matrix2[z][y]; product[x][y] = sum; } } void CGraphics2DView::Translate(MATRIX3X3& m, int xTrans, int yTrans) { MATRIX3X3 m1, m2; m1[0][0]=1; m1[0][1]=0; m1[0][2]=0; m1[1][0]=0; m1[1][1]=1; m1[1][2]=0; m1[2][0]=xTrans; m1[2][1]=yTrans; m1[2][2]=1; MultMatrix(m2, m1, m); CopyMatrix(m, m2); } void CGraphics2DView::Scale(MATRIX3X3& m, double xScale, double yScale) { MATRIX3X3 m1, m2; m1[0][0]=xScale; m1[0][1]=0; m1[0][2]=0; m1[1][0]=0; m1[1][1]=yScale; m1[1][2]=0; m1[2][0]=0; m1[2][1]=0; m1[2][2]=1; MultMatrix(m2, m1, m); CopyMatrix(m, m2); } void CGraphics2DView::Rotate(MATRIX3X3& m, int degrees) { MATRIX3X3 m1, m2; if (degrees == 0) return; double radians = 6.283185308 / (360.0 / degrees); double c = cos(radians); double s = sin(radians); m1[0][0]=c; m1[0][1]=s; m1[0][2]=0; m1[1][0]=-s; m1[1][1]=c; m1[1][2]=0; m1[2][0]=0; m1[2][1]=0; m1[2][2]=1; MultMatrix(m2, m1, m); CopyMatrix(m, m2); } void CGraphics2DView::Transform(SHAPE& shape, MATRIX3X3& m) { int transformedX, transformedY; for (int x=0; x<shape.numVerts; ++x) { transformedX = (int) (shape.vertices[x].x * m[0][0] + shape.vertices[x].y * m[1][0] + m[2][0]); transformedY = (int) (shape.vertices[x].x * m[0][1] + shape.vertices[x].y * m[1][1] + m[2][1]); shape.vertices[x].x = transformedX; shape.vertices[x].y = transformedY; } } void CGraphics2DView::DrawShape(CDC* pDC, SHAPE& shape1) { int newX, newY, startX, startY; RECT clientRect; GetClientRect(&clientRect); int maxY = clientRect.bottom; for (int x=0; x<shape1.numVerts; ++x) { newX = shape1.vertices[x].x; newY = maxY - shape1.vertices[x].y; if (x == 0) { pDC->MoveTo(newX, newY); startX = newX; startY = newY; } else pDC->LineTo(newX, newY); } pDC->LineTo(startX, startY); }
-
Load Graphics2DView.h and add Listing 3.22 to the top of the file, right before the CGraphics2DView class declaration.
Listing 3.22 New Code for the CGraphics2DView Class's Header File
typedef double MATRIX3X3[3][3]; typedef struct vector { int x, y, w; } VECTOR; typedef struct shape { int numVerts; VECTOR* vertices; } SHAPE;
-
Also in Graphics2DView.h, add Listing 3.23 to the CGraphics2DView class's Attributes section, right after the CGraphics2DDoc* GetDocument() const line.
Listing 3.23 New Code for the CGraphics2DView Class's Attributes Section
protected: SHAPE m_polygon; VECTOR m_vectors[4]; int m_rotate; double m_xScale, m_yScale; double m_xTranslate, m_yTranslate;
-
Again in Graphics2DView.h, add Listing 3.24 to the CGraphics2DView class's Implementation section, right after the protected keyword.
Listing 3.24 New Code for the CGraphics2DView Class's Implementation Section
void DrawShape(CDC* pDC, SHAPE& shape1); void Translate(MATRIX3X3& m, int xTrans, int yTrans); void Scale(MATRIX3X3& m, double xScale, double yScale); void Rotate(MATRIX3X3& m, int degrees); void Transform(SHAPE& shape, MATRIX3X3& m); void MultMatrix(MATRIX3X3& product, MATRIX3X3& matrix1, MATRIX3X3& matrix2); void InitMatrix(MATRIX3X3& m); void CopyMatrix(MATRIX3X3& dst, MATRIX3X3& src);
Your Graphics2D program is now complete. To create the application, press Ctrl+Shift+B. To run the application, press F5 on your keyboard.