You may work with one partner on this assignment. For this lab, you will extend
Lab 02 to make your objects more interactive. The basic idea is to add functionality to your
test_geometry code to handle mouse and keyboard events so that users can add shapes, remove shapes, and move shapes interactively with the mouse and keyboard. You will need you use some basic linear algebra and work with GLUT's event handlers.
Drawing Modes
Your program should support the following modes: Adding objects (with a minimal support of lines, triangles, rectangles, and circles), Deleting objects, Moving objects, and changing colors. The user can toggle the current mode using either the keyboard or through a menu. You only need to support one of these methods, but supporting both is a nice touch. You will need a way to remember the current mode, and this will likely be done with a global variable. I recommend using a enumerated type to store the mode in a sane way that is easy to remember.
/* define a new type draw_mode_t that stores the current mode*/
typedef enum GFX_DRAW_MODE{
NONE=0,
SELECTING,
MOVING,
DELETING,
ADD_CIRCLE
} draw_mode_t;
draw_mod_t currentMode=ADD_CIRCLE;
Adding shapes
If a user is in an Adding shapes mode, left mouse clicks should add shapes to the screen. For example, if the user is in add circle mode,
the first mouse click will set the center of the circle and the second mouse
click with define a point on the perimeter of the circle. At this point, your
code should create a Circle object using your lab02 code, add it to a vector of
current objects on the screen and draw the object. A similar pattern should be
followed for other shapes. In the case of triangles and rectangles, your must orient your shapes in a counter clockwise direction regardless of how the user clicks on the points. Your constructors should arrange the points to achieve the correct orientation.
Moving shapes
In moving shapes mode, a mouse click should determine if the click is within a shape and if so, the shape containing the point can be moved by dragging the mouse to a new location for the shape. To track the mouse, you can add a
glutMotionFunc callback that is triggered when the mouse is moving across the display. If multiple shapes overlap and contain the mouse pointer click, your code should move the object that was most recently added to the screen.
To implement this correctly, you will need to check if a point is inside a shape. Add a method virtual int inside(GfxPoint const &)=0; to the Drawable class and implement the method appropriately in each of the derived classes. You must add the following methods to the GfxPoint class:
- GLfloat distance(GfxPoint const & other); /*Computes distance between a point and another point*/
- int leftOf(GfxPoint const & from, GfxPoint const & to); /*returns
1 if point is left of the line oriented from the point from to the
point to */
leftOf should use the cross product for orientation tests. With these helper methods, writing the
inside method for
Circle and
Triangle should be easier.
Deleting Shapes
In Delete mode, clicking on a shape should remove it from the screen. You do not need to actually call delete on the actual
Drawable* corresponding to the object. Just set a flag in the object indicating that the object should not be drawn. This is typically called
lazy deletions where we just mark things as deleted and actually delete them later. If you are obsessed with cleaning, you may occasionally scan the vector, removing hidden objects, freeing memory, and repacking the vector, but this is a fair amount of unecessary work at this point. You should however free all the dynamically allocated memory before the program exits.
Changing colors
In change color mode, clicking on a shape should change its color. You should add a GLUT menu that allows the user to select a few (4 or more) predefined colors of your choice to use as the current fill color. You can add more colors or other ways of choosing colors as you see fit.
Coordinate Transformation
When you click on the window, the coordinates passed to the mouse, keyboard, or motion handlers in GLUT are in screen coordinates (think pixels), not world coordinates as defined by
gluOrtho2D. The object you draw are in world coordinates. You must write a function
GfxPoint screenToWorld(int x, int y) that converts from screen coordinates to world coordinates (assuming an Orthographic 2D projection). To further complicate things, the screen is left handed, meaning that
x increases from left to right, but
y increases from
top to
bottom. The orthographic projections is right handed, and
y increases from bottom to top. Glu allows you to do this conversion through some openGL and glu function calls, but you must do it manually.
You will need to store the bounding box of the viewport (in screen coordinates) and the bounding box of the projection (in world coordinates). You can use a struct to store this information.
typedef struct {
GLfloat xmin, xmax, ymin, ymax;
} boundingBox;
boundingBox worldBox, windowBox;
You should set up a reshape handler using
glutReshapeFunc() that updates these global bounding boxes as necessary.
Pan/Zoom
By storing a global bounding box representing the projection, it is fairly easy
to add support of zooming in, zooming out, and panning around the display. Add
support to your code to allow users to zoom in and out using the 'z'/'Z' keys,
respectively and to pan around the scene using up, down, left and right arrow
keys. As there is no ASCII code for up arrow, you can use another callback
handler
glutSpecialFunc(void (*func)(int key, int x, int y)) to
respond to these keys. See the
documentation
for the names of special keys. If you want to go crazy on keyboard shortcuts, you may be interested in
glutGetModifiers, for binding against things like CTRL-SHIFT-F1. To physically move the world view, make the appropriate modification to
gluOrtho2D.
Testing
Modify
test_geometry.cpp to create some geometric features and render
them on the screen (Translation: draw something in 2D). You may use
gluOrtho2D to set the region
coordinates. All of your objects should be stored in
an STL
vector<Drawable*> such that drawing the scene can be done
by simply looping over the vector and calling the
draw() method on
each object.
If you program dynamically allocates memory (it probably should) using new, be sure to free the memory by calling delete in the appropriate place.
If a class dynamically allocates memory, be sure to write an appropriate destructor.
If all is working well, you should be able to test your code using
cumin[labs]$ ls
03/ CMakeLists.txt
cumin[labs]$ mkdir build
cumin[labs]$ cd build/
cumin[build]$ cmake ..
cumin[build]$ make
cumin[build]$ cd 03
cumin[01]$ ./test_cad
Submit
Once you are satisfied with your programs, hand them in by typing
handin40 at the unix prompt.
You may run
handin40 as many times as you like, and only the
most recent submission will be recorded. This is useful if you realize
after handing in some programs that you'd like to make a few more
changes to them.