OpenGLSkeleton
Note:
The below is a copy of the Project 0 warm-up
project that OpenGLSkeleton was part of. You are welcome to download
the source and follow along, ignoring instructions that have to do with
project submission and grading. The source code on the OpenGLSkeleton
home page is an extended version of the solution to this set of instructions.
Back to the OpenGLSkeleton Home Page
Project 0
Warm-Up Project
This is a warm-up project, intended to allow you to become comfortable with
the structure of a Visual C++ program using MFC and OpenGL. Completion of
the entire project should take you about three hours, although you may
expect to go faster or slower depending on your level of experience with
Visual C++. You will be following the step-by-step instructions that
are given below.
This project consists of the following steps:
- Create a new project, using your first and last name and 0 for its
name. (e.g RyanHolmes0, for me)
- Follow the instructions to re-create the functionality of the provided
skeleton program (See below). This is primarily cut-and-paste.
- Add a menu item that opens the standard windows color chooser, allows
the user to select a color, and then changes the background color of the
window to that color.
- Add a menu item that toggles a boolean variable and refreshes the
image.
- Create a dialog that allows the user to set an integer variable.
- Add a menu item that displays your dialog, retrieves input, and refreshes the image.
And that's it!
Step-by-Step Instructions
Creating a Project
In this section you will create your own Visual C++ project, named after
yourself. Note that submissions named "project0" or "openglskeleton" or
anything other than your own name will not get full credit. If your name
is very long, you may abbreviate words.
- Start Visual C++
- Select New... on the File menu
- On the Projects tab, find "MFC AppWizard (exe)" and select it
- Under "Project name" put in your first name, your last name, and the
project number. (e.g. RyanHolmes0, for me)
- Under Location, pick a directory you want to work in.
- Hit Ok
- Change the type of application to Single document, leave the Doc/View checked, and hit Next
- Leave the database support at none, and hit Next
- Leave the compound support at none, and hit Next
- Uncheck Printing and Print preview, leave the rest, and hit Next
- Leave MFC Standard, source file comments, and shared MFC dll, and hit Next
- The wizard will show you the files you are about to create. Hit Finish. Visual C++ will create your project.
- Build and Run your program to see what it does. It isn't very exciting yet,
but it does have a working menus and an about dialog box already.
Adding Skeleton Functionality
In this section you will be copying in OpenGL code from the provided skeleton.
The code is well documented so that you can gain an understanding of what it is
doing. You are not expected to understand OpenGL or write your own OpenGL code
here. As you are copying, read the comments and take note of what types of
functions go in which file.
- Download the provided skeleton project and
unzip it into a different directory.
- You may want to compile the skeleton, see how it runs, and look it over
before proceeding.
- Open your project's StdAfx.h file (On the sidebar on the left, hit the
FileView tab, expand Header Files, and double-click the file)
- Open the skeleton's StdAfx.h file. I find that when I am copying and
pasting into a VC++ project, it is easier to have the project I am pasting
into open in VC++ and the files that I am copying from open in notepad or
another ASCII editor. This makes it easy to tell which file you want to paste
into and saves the trouble of switching back and forth between copies of
Visual C++
- Find the lines in the StdAfx.h that include the OpenGL files. Copy them
into your own.
- Select Settings... on the Project menu of your project. On the dialog
that opens up, first look in the upper left corner for the drop-down box
labeled Settings For: and change its entry to All Configurations. This
ensures that the changes you make will apply both to the Debug and the
Release versions of your project. Next find the Link tab on the right-hand
section of the dialog and in the Object/Library modules entry box
put "opengl32.lib glu32.lib". This
allows VC++ to find the functions that you just included when it compiles. Hit Ok
to close the dialog.
- Open OpenGLSkeletonView.h and your matching View.h file. Copy the
member variables and the single function declaration from the protected
area into yours. (From CClientDC* m_pDC to void setGLView(void))
- Open OpenGLSkeletonDoc.h and your matching Doc.h file. Copy the
three member variables from the protected area and the six function declarations
from the public area into their respective places. Add the #include for
math.h at the top of your header file.
- Open OpenGLSkeletonDoc.cpp and your matching Doc.cpp file. Copy
the initializer from the constructor into yours, the body of the destructor
into yours, and the entire functions setSubdivisions(), setInset, and
buildList() into yours. Note that you will have to change the names from
COpenGLSkeletonDoc::function() to match the name of your project. Now
your Document is complete.
- Open OpenGLSkeletonView.cpp and your matching View.cpp file. Copy
the contents of the constructor, the PreCreateWindow function, and the
OnDraw function into yours. In each function that you copy, check to
see if it references COpenGLSkeletonDoc. If it does, change that reference
to one to your own Doc (e.g. CRyanHolmes0Doc).
- Now, with the View open, select Class Wizard from the View menu. Find
the Message Maps tab. When any one of these messages occurs, your class
can respond to them with a function. You determine how the messages map
to your functions here.
- With the name of your View class selected under Object IDs, go through
the Messages list on the right. Find each of the following messages,
select them, and then with them selected, press the Add Function button.
This will add an empty function for you to fill to your class, and set
up the compiler to arrange for that function to be called when that message
occurs. Add functions for: OnInitialUpdate, WM_CREATE, WM_DESTROY, WM_ERASEBKGND
and WM_SIZE. You should see that a few already exist for you, such as
OnDraw. Notice that for the Windows Message functions, it creates a
more reasonable name: OnCreate, not OnWM_CREATE.
- Close the Class Wizard by hitting Ok. Your new functions will appear
at the bottom of your View class.
- Copy the contents of each matching function from OpenGLSkeleton to
your own View's functions.
- Copy the setGLView() function into yours, remembering to change the
name to match your own project.
- Build your project. Compile errors show in the box at
the bottom of the screen. Scroll it up and down to view any errors. If
there is one, you can double-click it in this box, and it will show you
the line of code where the error is.
- After you've built the project, run it and see what shows up. You should
see a blue 3D cube on a black background in the center of the window. If
not, read back and see if you missed any steps.
A Detour for Some Explanation
The text book uses the GLUT windowing system. It functions very similarly
to the MFC system, but with a bit less sophistication and a lot less
complexity. The functions that you added have very close correspondence
to the GLUT functions used in the text. OnDraw is called when the window
needs to be re-drawn. This is like the glutDisplayFunc() callback in GLUT. If
you want a display to be done, you can call Invalidate, which is like
the glutPostRedisplay() call. OnSize, which you added for the WM_SIZE
message, is called when the window size is changed. This is like the
glutReshapeFunc callback. The OnPreCreate and OnCreate functions are
called while the window is being created, allowing it to be customized
to use OpenGL. OnDestroy is called when the window is being destroyed
(The program is quitting), so that the OpenGl resources can be released.
You will see glutKeyboardFunc and glutMouseFunc callbacks in the book,
these roughly correspond to the OnKeyDown, OnLMouseDown, OnLMouseUp,
etc. functions you can add through the Class Wizard. The MFC functions
are much more specific, and you can do something like OnLButtonDblClk
to respond only to the left mouse button's double clicks, which would
take a bit of tinkering in GLUT.
Adding a Color Chooser
In this section you will add a menu item and attach a menu handler
to it. The menu handler will use a standard Windows dialog box (The
color chooser) to get input from the user. You will then use that input
to change the OpenGL state.
- Open your project in Visual C++. On the left, where the FileView is,
there are two more tabs. Select the ResourceView tab.
- These are the resources used by your program. Lots of interesting
things can be found here, but for now expand the Menu list, and double-click
on the IDR_MAINFRAME entry.
- This opens the menu for the main frame in the menu editor. The shaded
outline of a box indicates a place for you to add something. Take the box
and drag it between View and Help
- Right-click on the box and select properties, or just double-click it.
This allows you to change things about the menu item.
- Under Caption, type in "&Tools" (Or your name, or whatever you like).
The & means that the following letter is the short-cut key - You can open
this menu by hitting Alt-T as well as by selecting it with the mouse. Hit
Enter
- Now you have an empty sub-menu. Open this one and again select the
properties. For the ID put in ID_TOOLS_BGCOLOR (Or, again, something that
seems reasonable). By convention, menu IDs are in all caps, begin with
ID, have separate words separated by underscores, and include the name
of the menu (TOOLS, in this case). The ID is global to your project,
and you'll need to find it in a list later. Naming them MENU1, MENU2,
etc. wouldn't be a good idea.
- For the Caption, put in "Change background color...". You could put
an & in there for a keyboard shortcut if you wanted. Hit Enter.
- Now the menu entry is complete. Close the menu editor and open your
View.cpp file.
- From the View menu, open the Class Wizard. Make sure that the Class
Name is your View class. (Select the View from the drop-down box if it
isn't)
- On the left are the Object IDs. Scroll down until you find the one
you just created. Select it. The right side changes, listing two
messages you can map for that ID. Select COMMAND, Add Function, confirm
that function name, and hit OK to close the Class Wizard.
- Scroll down to the bottom of your View and find the new function.
This function will be called whenever that menu item is selected.
- First, we need to create the dialog that we want to open. This is
a standard Windows dialog, and is all set up for us. We will create it
in one line, like this: "CColorDialog dialog(bgColor);". This creates
the dialog and at the same time tells it that the current color it has
selected is our current background color.
- Now we want to display the dialog so the user can use it. We do
this with an if statement: "if (dialog.DoModal() == IDOK) {}". DoModal
makes the dialog appear, and doesn't return until the user has finished
with the dialog. When they've finished, they will hit the OK button or
the Cancel button. If they hit Cancel, we don't want to do anything.
- If they hit OK, we want to know what color they picked. CColorDialog
has a convenient function for returning this color. We add "bgColor = dialog.GetColor()"
in the if block.
- Now that we know the color we need to tell OpenGL about it. Find the
command up in the OnInitialUpdate function that sets OpenGL's clearing color and
copy it down into the if block.
- And finally, we need to tell MFC to draw the window again, because
we've changed something. Add "Invalidate(FALSE);" after the OpenGL call,
inside the if block.
- Now we're done. The dialog is local to the function, so is destroyed
when the function exits, and we've saved the color for the next call.
Compile it and run it, and see how it works.
Toggling Inset Squares
In this section you will add a menu item and menu handler that toggle
a boolean variable. You will also add a menu update handler that allows
the user to easily see the state of the boolean variable when they view
the menu.
- Return to the IDR_MAINFRAME menu-editing screen
- Add a new menu item on the Tools menu that you created before by
right-clicking or double-clicking the ghost menu item on the Tools menu.
- Under ID put in ID_TOOLS_INSET.
- Under Caption put in "&Inset squares"
- Hit Enter to save the new menu item. Close the menu editor and open your View.cpp.
- From the View menu, open the Class Wizard. Make sure that the Class Name is your View
class. (Select the View from the drop-down box if it isn't).
- Scroll down through the Object IDs on the left until you find ID_TOOLS_INSET
and select it.
- Select COMMAND, then press the Add Function button, and confirm the
function name.
- Now also select UPDATE_COMMAND_UI, Add Function, and confirm that function
name as well.
- Close the Class Wizard with the OK button to create your functions.
- Scroll down to the bottom of the View and find the two new functions. The first
function, like the background color function, is called when your menu item is
selected. The second function is called each time the menu item is displayed
by the UI (User Interface).
- Let's start by filling in the UI function. First, we need to get a pointer
to the Doc. Do this as is done in the first three lines of OnDraw().
- Next, we want to update the UI depending on the value of pDoc->isInset().
Create an if (pDoc->isInset()) block with an else block.
- The pCmdUI variable passed to this function is a pointer to the UI object
that is linked to this function, in our case the menu item. For the true case
of the if block, put in the line pCmdUI->SetCheck(TRUE), and for the else
case put in pCmdUI->SetCheck(FALSE). This will put a check mark next to the
menu item if isInset is true, and will remove it otherwise. This is all the
function needs to do.
- Now we'll fill in the actual menu handler. First, get the pointer to the
Doc as you did in the UI handler.
- First we want to toggle the value of the inset variable. We can do this
with the following line: pDoc->setInset(!pDoc->isInset()); This seems a little
extreme, but the variable is protected inside the Doc so that it must be
read and modified with the access functions. The reason for this is that the
Doc must call buildList internally when the value of the variable changes.
- Now that the variable has been toggled and the list has been re-built, we
need to do one further thing in the menu handler. OpenGL will only actually
render the new list when OnDraw is called. To get MFC to call OnDraw again,
add Invalidate(FALSE);, as we did for the background color.
- Compile and test your menu handler.
Creating a Dialog
In this section you will create a simple MFC dialog using Visual C++'s
dialog editor. This is a very powerful editor, and you may want to play
with some of the other controls if you have the time.
- Now it's time to get creative. Go to the Insert menu, and select
Resource. Select Dialog from the list. Don't expand the entry, we
only need a basic dialog. Hit New to create it.
- A new dialog is created and opened for editing. Notice that it
appears in your ResourceView under the name IDD_DIALOG1. This is probably
not what you want to keep calling it. Right-click on the dialog and
open the properties. This is generally like a menu item, but with more
options. Change the ID to something like IDD_SUBDIVISION_DIALOG and the
Caption to something like "Change Subdivisions". Remember to hit Enter to
close the properties box - If you hit the x, it cancels changes.
- You should have a floating toolbar of Controls on the right-hand side.
If for some reason it isn't there, right-click on any empty space on a
toolbar you can find (The menu at the top is usually good). From the
list that comes up, select Controls.
- You can now draw controls onto the dialog. You are going to put two
controls onto the dialog.
- First, put a Static Text control into your dialog. Select the Static
Text control tool from the toolbar (Labeled Aa
on the toolbar). Draw a rectangle on your dialog with the mouse where
you want the control to be placed. When you let up the mouse, the control
will be created for you with default values.
- Right-click the control and select Properties. Change the Caption to
"Change the number of subdivisions of the rendered object.".
- Next, put an Edit Box control in the dialog (Labeled ab|).
- Right-click the control and select Properties. Change the ID from
IDC_EDIT1 to IDC_SUBDIVISIONS.
- Experiment with the layout controls on the Layout menu. You can align
controls, change the tab order, and test the dialog from this menu.
- When you have your dialog all laid out as you like it, double-click
on an empty space on it. You will get a message about your dialog being
a new resource. You want to create a new class for it, so hit OK.
- Name the class something useful (SubdivisionsDialog perhaps), and
hit OK.
- You will find yourself in the Class Wizard. Go to the Member Variables
tab.
- You should see your controls, listed by ID, on the left. Find
IDC_SUBDIVISIONS and select it if it is not selected. Hit Add Variable.
For the member variable name put in m_subdivisions. Leave the Category
on Value but change the Variable type to int. This will cause MFC to
automatically check your box, make sure it's an int, convert it, and
store it in that variable, when the user hits OK. If it isn't, the
user will get an error and be asked to enter an integer. Very slick.
Hit OK on the Add Variable dialog and return to the Class Wizard.
- On the bottom of the Class Wizard now you will see two edit boxes,
Minimum value and Maximum value. Put 1 in the Minimum Value and 128
in the Maximum value. This will cause the dialog to reject values
outside this range.
- Now hit OK on the Class Wizard. It will create your class and you
have a usable dialog.
Using Your Dialog
In this section you will connect the dialog that you created
to a menu item/menu handler and react to the input received from
the user through the dialog.
- Add a new menu entry as you did for the color chooser, and add
the handler function to your View class.
- Before you fill in the handler, open the View.h file and #include
the .h for your dialog class. Otherwise the compiler will not be able
to find your dialog's class.
- In the handler, you will follow the same basic steps as the color
chooser. First, get a pointer to the Doc.
- Next, create a local instance of the dialog class. To do this,
declare a variable of that class. Note that, unlike in Java, declaring
a variable with a type of a class automatically calls the constructor
for that class and creates the object.
- Now you want to set the dialog to show the current value of the
number of subdivisions. You can get the current value with pDoc->getSubdivisions().
To set the dialog's value, you use the member variable m_subdivisions
that you created in the dialog with the Class Wizard.
- Now call DoModal() on your dialog and make sure they didn't cancel out.
- If they hit OK, use pDoc->setSubdivisions() to send the new value to
the Doc, then tell MFC to refresh the screen. You should be able to
figure out how to do this by now.
- Compile, test, and play!
Congratulations
You've got a working 3D OpenGL program!
Back to the OpenGLSkeleton Home Page