Programming Assignment 4
CS 45500
Computer Graphics
Fall, 2025

This assignment makes use of the files contained in this zip file. This assignment is due Friday, December 19.

For this assignment, you will write an event-driven program that responds to mouse, keyboard, and window events. This assignment is based on the material in renderer_5.zip and Readme_r5_view_volumes.md.

In the zip file there is an executable demo program, hw4_demo.jar, that you can run by double clicking on hw4_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 hw4_demo.jar

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

The program hw4_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 the program window, the shapes stop moving.

The hw4_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 hw4_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 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_5. See the file Readme_r5_view_volumes.md in the folder renderer_5.zip

To make your version of the program respond to all these events (keyboard, mouse, and window events), you need to write appropriate event handlers that implement the KeyListener, MouseListener, MouseMotionListener, and ComponentListener interfaces. Outlines of these interface implementations are already in the Hw4.java file. You need to complete the code for the mousePressed(), mouseReleased(), mouseExited(), mouseDragged(), and componentResized() methods. (The keyTyped() method is implemented for you.) I have written the Hw4.java program to implement the event handlers as methods in the Hw4 class (the Hw4 object is the event listener object for all the events).

To see more examples of event handlers that are similar to the way Hw4.java is set up, look at the client programs in the clients_r5 sub-folder of renderer_5.zip.

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 camera's image-plane. When you run the demo program, you can see these conversions in the mouse debugging output (the 'i' keyboard command).

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 720 pixels wide and 720 pixels tall. That means that, initially, the camera's view-rectangle is 720 / 80 = 9 units wide (from -4.5 to 4.5 along the x-axis) and 720 / 80 = 9 units tall (from -4.5 to 4.5 along the y-axis). When you resize the program's window, you are resizing the FrameBuffer, which means that you need to resize 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 Hw4.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 helper methods.

In particular, notice the data structure defined at the end of the program, the ModelInfo class. It is used to store information about each shape as the shape gets dragged around the image-plane. The Hw4.java program creates a List of these data structures, called infoList. Some of your event handlers will update information in this list and other event handlers will look up information from this list.

Your second step 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 System.out 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 each ModelInfo object there is a "hit function" defined as a lambda expressions. Each geometric shape's hit function is a boolean valued function that determines if a point from the image-plane is inside of the shape. For convenience, the lambda functions are "wrapped" in a regular instance method called hitBy(). Iterate through infoList and call each shape's hitBy() method to see if that shape has been hit by the point representing the mouse press. Store the boolean result in the hit field of the ModelInfo object. Print to System.out 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 the 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 System.out 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 System.out. 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 any 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 Hw4.java program contains event handlers that print the event objects to System.out. This lets you see all the events that are happening. When you write the code that implements one of the event handlers, comment out the line that prints out the event object. When you program is finished, make sure it acts like the demo program.

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 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));

When you convert FrameBuffer dimensions to view rectangle dimensions, use the scaling factor that 80 pixels in the FrameBuffer is equivalent to 1 unit in the image-plane. After updating the view rectangle, the Scene needs to be rendered again.

The Hw4.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. You do not need to write a lot of code. Each blank space in a method is a strong hint for how many lines of code you need to write.

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 CS455Hw4Surname.zip (where Surname is your last name) containing only your version of Hw4.java.

This assignment is due Friday, December 19.