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

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

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

In the zip file there is an executable jar file, hw3_demo.jar, that you can run. There is also a program file Hw3.java. You need to complete the program file Hw3.java so that it runs the same way as the demo program.

The program hw3_demo.jar lets you click on 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.

You will need to implement the KeyListener, MouseListener, MouseMotionListener and ComponentListener interfaces. Outlines of these interfaces are already in the Hw3.java file. You need to write code for (at least) the keyTyped(), mousePressed(), mouseReleased(), mouseExited(), mouseDragged(), and componentResized() methods. I have outlined the Hw3.java program to implement the event handlers in the style where the class Hw3 implements all the event listener interfaces, so an instance of the class is its own event handler object.

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 in renderer_2.zip, like InteractiveCube_R2.java or InteractiveTriangle_R2.java, and also look at the example InteractiveFrame_v3.java from java_gui_intro.zip.

Here are some suggestions for writing your program. You have to write this program in several (many) stages. First, give the program very basic event handlers that just print the event objects to stdout. Then, use the MouseEvent api to print out just the information that is relevant (like the x and y coordinates of a mouse click). After you get the pixel coordinates of a mouse click, you need to transform the pixel coordinates into the corresponding (x, y, z) coordinates in camera space. The geometric objects are all in the z = -10 plane, so you need to transform the 2-dimensional pixel coordinates into the appropriate (x, y) coordinates of the z = -10 plane of camera space. Print this information to stdout and then click on several obvious points in the window (like the corners of the squares) and make sure your coordinate transformation is correct.

Here is a bit more detail about converting pixel coordinates to camera space coordinates. In the zip file there is a document called CS45500-renderer-formulas.pdf. This document summarizes the mathematical formulas used in the rendering pipeline. The job of the rendering pipeline is to take a point in camera space, project it to a point in the viewplane, take that point in the viewplane and transform it to point in the pixel-plane, and then map that point in the pixel-plane to a pixel in the framebuffer. Your Hw3 program needs to do these exact same step, but in reverse. You start with a pixel in the framebuffer. You need to figure out which point in the pixel-plane represents that framebuffer pixel. Then you need to transform that point in the pixel-plane back to a point in the viewplane, and then back, one more time, to a point in camera space so that you can figure out, in the z = -10 plane, which shape, if any, was clicked on. Do these steps using the inverses of the formulas given in CS45500-renderer-formulas.pdf.

When you can click on a point and get its correct camera space coordinates, then you are ready to determine if you are clicking inside of a geometric shape. You know the location and size of each shape in the z = -10 plane. So you should be able to tell if a mouse click is inside a shape. For each shape, write a boolean hit() method that determines if a mouse click "hits" that shape. Iterate through scene.modelList, check each shape to see if it has been hit and print to stdout the name of every model that is hit (this code should be in the mousePressed() method). Here is an important hint. You probably don't want your hit() methods to work directly with the 3D models themselves since those models are meant for 3D rendering not hit calculations (all the vertices and line segments in the Circle object are not going to be of much use when you want to see if you clicked within the circle). It would be a good idea for you to create another representation (data structure) for each shape that would be easier to work with when determining hits. For example, for the circle, to check for a hit all you need to know are the circle's center and radius. For each square, you need to know its center and the length of a side. For the diamond, you need to know its center and its "radius" (the distance from the center to a corner). The diamond shape is more like a circle than you might think; they are both superellipses and there is an equation for the diamond similar to the equation for a circle.

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 first need to write a simple combination of mousePressed(), mouseDragged(), and mouseReleased() methods that just keeps 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. Then print all that information in both pixel coordinates and camera coordinates. Then compute the "distance traveled" (in camera coordinates, in both the x-direction and the y-direction) by the mouse between calls to mouseDragged() and print this to stdout. The distance traveled by the mouse in camera coordinates 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 x and 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 x and y coordinates of each vertex in the model by the distance (in camera space) that the mouse moved. Be sure to also update your alternative representation for each dragged model. After updating each dragged model, the scene needs to be rendered again. Your mouseDragged() method should end with a block of code like this.

    // Render again.
    FrameBuffer fb = fbp.getFrameBuffer();
    fb.clearFB();
    Pipeline.render(scene, fb);
    fbp.update();

You should work on the above steps first for just one single shape (either the circle or one of the squares). After you can drag around one shape, extend your code so that it works with all the shapes. Also, 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 (which happens a lot to all of us).

The next feature you need to add to your program is to allow the user to resize (and reshape) the window. Try doing this with the demo program. You need to implement the componentResized() method in the ComponentListener interface. Your implementation of this method can be exactly like the implementation in the example programs from renderer_2.zip. What you really need to do is modify your code (your formulas) that transforms pixel coordinates to camera coordinates. This code needs to know how many pixels there are going across the view rectangle in each of the x and y directions. One very important thing to realize is that when the user resizes the window and sees the circle become an ellipse, that circle is still a circle in camera coordinates! You do not need to change your hit() methods for determining if a point in camera coordinates is within one of the shapes.

The last feature you need to add to your program is to implement the 'r' and 'R' commands. When a window is resized into a rectangular shape, the squares no longer appear square and the circle no longer appears circular. Putting the framebuffer back into a square shape is tricky to do with the mouse. So the 'r' and 'R' commands have your code make the framebuffer square again. The 'r' command should make the framebuffer square using the minimum of the framebuffer's current width and height. The 'R' command should make the framebuffer square using the maximum of the framebuffer's current width and height. The code to do this is similar to what is in the componentResized() method. You create a new FrameBuffer object with the desired dimensions, then give the new FrameBuffer object to the FrameBufferPanel. But, unlike componentResized(), you need to tell the JFrame that holds the FrameBufferPanel to repack() itself.

Here is one little detail that you will need to deal with. When you click on the mouse, you want the pixel coordinates that Java gives you to be relative to the FrameBufferPanel within the window, not relative to the whole Java window. Another way to put this is that pixel (0, 0) should be the upper left hand corner of the framebuffer, not the upper left hand corner of the title bar of the JFrame window. You get the correct pixel coordinates by registering the mouse listeners with the FrameBufferPanel. If you register the mouse listeners with the JFrame, then you will get the slightly off pixel coordinates that are relative to the whole window.

Here is another useful idea. The steps above tell you to print out a lot of information to stdout. All of that output is useful, so you want to keep the code that produces it. But you don't always want to see all that output. Use the keyboard command i (or add several other keyboard commands) to turn on and off your "debugging" output. (Experiment with the demo program to see an example of some debugging information.)

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

This assignment is due Thursday, October 29.