Programming Assignment 3
CS 45500 / CS 51580
Computer Graphics
Fall, 2024

This assignment makes use of the files contained in this zip file. This assignment is due Thursday, November 14.

For this assignment, you will write an event-driven GUI program that responds to several kinds of mouse, keyboard, window, and component events.

In the zip file there is an executable demo program, hw3_demo.jar, that you can run by double clicking on hw3_demo.cmd (on Windows). On Linux or Macs (and also on Windows) you can run the demo program from the command-line with this command.

        > java -jar hw3_demo.jar

In the zip file there is also a Java source file Hw3.java that you need to complete so that it runs the same way as the demo program.

The program hw3_demo.jar lets you click on five geometric shapes and drag them around the window. If you click on a point that is inside of several (overlapping) shapes, then all of the shapes will drag around together. When you release the mouse, or if the mouse moves off of the program window, the shapes stop moving.

The hw3_demo.jar program also responds to several keyboard commands. The keyboard commands are documented in a help message displayed in the program's console window.

The program hw3_demo.jar responds to its window being resized. If you enlarge the program's window, then the program shows you more of the camera's image-plane (the camera's view-rectangle grows with the program's window). If you shrink the program's window, then the program shows you less of the camera's image-plane (the camera's view-rectangle shrinks with the program's window). If you click on a shape and then use the 'i' keyboard command, then in the console window you will see the image-plane coordinates of every mouse click. If you use the 'j' keyboard command, then in the console window you will see the image-plane coordinates of every mouse movement. When the mouse leaves the program's window, the last mouse coordinate tells you the image-plane coordinate of that edge of the program's window (that is, the coordinate of the corresponding edge of the camera's view-rectangle). Changing a camera's view-rectangle is a feature of renderer_7. See the file Readme_r7_view_volumes.txt in the folder renderer_7.zip

The program hw3_demo.jar also has a few GUI components. There are five radio buttons (one for each shape), a drop down list (that lets you change the color of the shape selected by the radio buttons), a button to reset the location of the five shapes, and a button to take a "screenshot" (which will appear as a png image file in the program's directory).

To make your version of the program respond to all these events (keyboard events, mouse events, window events, radio button events, combo box events, and button events), you will need to write appropriate event handlers that implement the KeyListener, MouseListener, MouseMotionListener, ComponentListener, and ActionListener interfaces. Outlines of these interface implementations are already in the Hw3.java file. You need to complete the code for (at least) the keyTyped(), mousePressed(), mouseReleased(), mouseExited(), mouseDragged(), componentResized(), and actionPerformed() methods. I have outlined the Hw3.java program to implement the event handlers in the style that uses inner (nested) classes.

If you want to see more examples of event handlers that are similar to the way Hw3.java is set up, look at the example programs EventExperiment_ver4.java and EventExperiment_ver5.java from the how_to_handle_handlers sub-folder of JavaGuiEvents.zip.

You can see several more examples of event handlers in the clients_r2 sub-folder of renderer_2.zip (for example, InteractiveCube_R2.java or InteractiveTriangle_R2.java) but those examples set up the event handlers in a slightly different style.

When you run the program, each of the five shapes that you see on the screen is a Model in the program's scene graph. The Camera for this program is an orthographic camera, so we can think of these shapes as being in the two-dimensional image-plane of the camera (instead of being in three-dimensional camera space). When you click on a point in the program's window, you are clicking on a point in the FrameBuffer, but your program really needs to know what point in the camera's image-plane you are clicking on (because the shapes are in the image-plane). Your program must convert mouse clicks in the FrameBuffer into points in the camera's image-plane. When you drag a shape in the program's window, you are really translating the shape's model in the camera's image-plane. Your program will need to convert mouse displacements in the FrameBuffer into distances in the image-plane. When you run the demo program, you can see these conversions in the mouse debugging output.

In this program, the center of the FrameBuffer will always correspond to the center of the camera's image-plane. The camera's view-rectangle will always extend from the FrameBuffer's left edge to its right edge. And the camera's view-rectangle will always extend from the FrameBuffer's top edge to its bottom edge. For this program, each unit of distance in the image-plane will be 80 pixels in the FrameBuffer. Initially, the program has a FrameBuffer that is 800 pixels wide and 800 pixels tall. That means that, initially, the camera's view-rectangle is 800 / 80 = 10 units wide (from -5 to 5 along the x-axis) and 800 / 80 = 10 units tall (from -5 to 5 along the y-axis). When you resize the program's window, you are resizing the FrameBuffer, which means that you are also resizing the Camera's view-rectangle. In the demo program, you can see this by resizing the window and then using the 'w' key command.

Here are some suggestions for writing your program. You have to write this program in several steps. Your first step is to read this entire document while looking over all of the code in Hw3.java. Look at how that code is organized. It builds the scene graph, then it builds the gui components, then it defines the event handlers. After the event handlers there are a few utility methods.

Your second step is to create the two JButtons for the GUI panel. Without these two buttons, the program will crash from a NullPointerException. You do not yet need to make the two buttons do anything, just instantiate them.

The third step of your program is to take the mouse clicks (in the FrameBuffer) and translate them into points in the camera's image-plane. This is done in the mousePressed() method of the MouseListener interface. After you get the pixel coordinates of a mouse click, you need to transform the pixel coordinates into the corresponding (x_ip, y_ip) coordinates in camera's image-plane. Print this information to stdout and then click on several obvious points in the window (like the corners of the window) and make sure your coordinate transformation is correct (you should compare your results to the demo program's results).

When you can click on a point and get its correct image-plane coordinates, then you are ready to determine if you are clicking inside of a geometric shape. In the hitFn array there are five lambda expressions, one for each geometric shape. These are boolean valued functions that determine if a point from the image-plane is inside of the corresponding shape. You call these functions using an unusual syntax. For example, this line calls the fifth hit function (for the circle) and tests if point_ip is in the circle.

     hitFn[4].test(4, point_ip)

Iterate through the hitFn array and check each shape to see if it has been hit by the point representing the mouse press. Store the boolean results in the hit array and print to stdout the name of every model that is hit (this code is still in the mousePressed() method).

When you can determine if a mouse click is within a shape, you are ready to start working with the mouseDragged() method. A user will press down on the mouse button, drag the mouse, then release the mouse button. You get a call to mousePressed() when the user presses down on the mouse button and you get a call to mouseReleased() when the user releases the mouse button (or a call to mouseExited() if the dragged mouse leaves the window). Between the calls to mousePressed() and mouseReleased(), while the mouse is being dragged, you will get calls to mouseDragged(). Each call you receive to mouseDragged() represents some amount of movement of the mouse, sometimes its just one pixel worth of movement, sometimes it is dozens of pixels worth of movement. You need a combination of mousePressed(), mouseDragged(), and mouseReleased() methods to keep track of the pixel coordinates of where the mouse is when its pressed, where the mouse is currently at each call to mouseDragged(), and where the mouse is when the mouse is released. Print all this information to stdout and get a feel for how mouse dragging works (compare your output with that form the demo program). Print all that information in both pixel coordinates and image-plane coordinates. Then compute the "distance traveled" (in image-plane coordinates) by the mouse, in both the x-direction and the y-direction, between calls to mouseDragged() and print this to stdout. The distance traveled by the mouse in the image-plane is vital for being able to move a shape by the appropriate amount.

Now you know when a mouse press lands within a shape and how far a mouse drag moves. So now you can take the delta-x and delta-y distances traveled by the mouse (between calls to the mouseDragged method) and use them to update the location of a shape that was hit. In the mouseDragged() method, you need to update the translation Vector for each model that was hit by the last mouse pressed event. You update the translation Vector of each hit model by the distance (in the image-plane) that the mouse moved. After updating the translation Vector of each dragged model, the scene needs to be rendered again with this block of code.

    final FrameBuffer fb = fbp.getFrameBuffer();
    fb.clearFB();
    Pipeline.render(scene, fb);
    fbp.repaint();

As you complete these small steps, it is a good idea to save each small step as an appropriately named file. That way you have both a reminder of how your program was built up and something to fall back to when a mistake makes things hopelessly confused.

Notice that the Hw3.java program contains basic event handlers that print the event objects to stdout. When you write the code that implements one of the event handlers, comment out the line that prints out the event object.

The next feature you need to add to your program is to allow the user to resize the window (try doing this with the demo program). You need to complete the implementation the componentResized() method in the ComponentListener interface. As we mentioned earlier, resizing the program's window means resizing the Camera's view-rectangle. You need to use the FrameBuffer's new dimensions to compute new values for the Camera's left, right, bottom, and top parameters. The Camera and the Scene objects are both immutable, so you change the Camera's parameters by creating a new Scene object that contains a new Camera object with this line of code.

     scene = scene.changeCamera(Camera.projOrtho(left, right, bottom, top));

After updating the view-rectangle, the scene needs to be rendered again.

The next step is to implement the event handlers for the GUI components in the program's side panel. The radio buttons, drop-down list, and regular buttons all use instances of the ActionListener interface.

The five radio buttons all share a single event handler. That event handler should update the currentModel field and then update the color name displayed by the combo box. Use the setSelectedInde() method in JComboBox to update the color name shown in the combo box.

The Java documentation has sample code for working with radio buttons and combo boxes.

The event handler for the JComboBox needs to change the color of the Model selected by the JRadioButton group. The JComboBox tells you the string name of the selected color. You need to use the currentModel field as an index into the colorChoiceIndex array and set the current model's color choice to the index of the color selected in the combo box. Then change the color of the current model. The renderer library contains a static utility method, renderer.scene.util.ModelShading.setColor(Color c), for changing the color of a model.

Next, you need to implement the ActionListener handlers for the screenshot and reset buttons.

Finally, you need to implement the keyboard commands for screenshot (the '+' key) and reset (the '=' key) in the keyTyped() method from the KeyListener interface.

The Hw3.java program has additional information in it about each of the above steps. Some of these steps have some of the code written for you. Otherwise, the steps leave blank spaces for you to fill in.

Don't be surprised if you need to read this assignment description many times. There is a lot of information here.

Turn in a zip file called CS455Hw3Surname.zip (where Surname is your last name) containing your version of Hw3.java.

This assignment is due Thursday, November 14.