Write a program that allows the user to draw free-hand shapes with the mouse: whenever the left mouse button is down, mouse motions leave a colored track. When the left mouse button is up, mouse motions have no effect. Use the example code in Section 3.5 of the textbook to get you started.
The sample code in the textbook uses the width and height of the
window (in pixels) as both window coordinates and model coordinates
(the only difference is that window coordinates have y=0 at the top,
and model coordinates have y=0 at the bottom, hence the mysterious line
of code y = wh - y;
).
Modify your program so that the model coordinates are
floating-point numbers in the range 0-1, while the window coordinates
remain integers in the range 0-500 (or whatever the current window size
is). This will help you keep model coordinates and window coordinates
distinct in your mind.
Add a window-reshape callback function, like that in the textbook, but bearing in mind the distinction between model and window coordinates. There are several ways you can do this:
Have the model coordinates always run from 0 to 1 in both dimensions. This is no problem as long as everything you draw is freehand, but if you add a command to draw (say) a circle, it'll come out squished in proportion to the shape of the window, rather than truly circular.
Have the smaller dimension always correspond to 0-1, with the larger dimension computed to maintain an aspect ratio of 1. This way, circles will be circular, regardless of the shape of the window.
Decide on a fixed scale factor, which will apply in both x and y dimensions regardless of the shape or size of the window.
Provide the ability (e.g. with a menu, section 3.6) for the user to choose among several possible scale factors.
object permanence.
If you draw directly into the graphics frame buffer, then every
time the window is resized, or covered by another window and uncovered,
or minimized and restored, etc. you'll lose whatever you had drawn
before. The usual way to solve this problem is called
"damage and redraw":
maintain a modifiable internal model, make all modifications to it
rather than to the frame buffer, and have a display callback simply
draw the current state of the internal model. Note that whenever you
change the internal model, you have to tell OpenGL that the model has
changed (e.g. by calling glutPostRedisplay()
), or your
change won't be visible on the screen until the next time the window is
resized, covered and uncovered, etc. Likewise, if the user gives a
command to change the scaling factor, you need to call
glutPostRedisplay()
to cause the window to be redrawn with
the new scaling factor.
One approach to implementing the internal model is described in section 3.9, which also allows the user to delete individual parts of the model (s)he has previously created. This approach works best with simple geometric shapes, but you can define a type of object to represent a freehand-drawn curve (presumably stored as a list of vertices).
Add a keyboard callback function, e.g. to quit the program when the user types the letter 'q'. You could also use the '+' and '-' keys to change the scale factor. Again, the effect won't be visible unless you've implemented object permanence.
Add the ability for the user to create circles, rectangles, squares, line segments, etc. of fixed size and shape. The sample program in section 3.8 (and appendix A.6) does this with a tool palette, so that a mouse-click in any of the boxes of the palette allows the user to select what shape to create next; one or more subsequent mouse-clicks in the main portion of the screen create the shape. Obviously, you won't learn much from simply copying paint.c from the Appendix; I'd like you to try to write as much as possible of this from scratch, turning to the Appendix code only for help with specific problems.
Add the ability for the user to specify the size and shape of new geometric objects by rubber-banding. For example, one might create a circle by clicking on the center, then holding the mouse button down while dragging to some distance away, which will become the radius; while the mouse is being dragged, the display updates continuously to show what the circle would look like if the mouse button were released right now. One might create a rectangle by clicking on one corner and dragging to the opposite corner; again, the display is continuously updated to show what the rectangle would look like if the mouse button were released now.
Add the ability for the user to select an existing shape and delete it (e.g. by pressing the DEL key on the keyboard). In the damage-and-redraw approach, this simply requires deleting the shape from the internal model and telling OpenGL to redraw everything.
Use display lists to "pre-compute" some of the work:
every time you create a complex object, assign it a unique identifying number
and compile a display list for it. The display callback function will
then simply invoke glCallList()
for the identifying numbers
of each of the objects that still exist in the model (i.e. haven't been
deleted); if these numbers are stored in an array, it can be done very
easily using glCallLists()
.
Note that this enhancement probably won't make a visible difference unless you're running the program in a client-server context, especially over a slow connection. If you are, you may save considerable bandwidth by sending only the identifying numbers of objects, rather than all the primitives that make them up.
Add the ability for the user to select an existing shape and make it start or stop rotating (as described in section 3.10.1).
Add double-buffering (section 3.10.2), so the animation looks smooth, even if you have a lot of objects rotating at the same time.
Add a timer callback (section 3.10.3), so you can control how fast things rotate.