Due Wednesday 6 March 2019
The goal of this week's project is to build a class for creating and managing the viewing parameters and view transformation matrix.
The main result of your work this week will be a View class that holds the current viewing parameters and can build a view transformation matrix [VTM] based on the parameters. You will also use the View matrix in a simple GUI to create a set of axes that respond to user input to translate, scale, and rotate.
You can use this skeleton file as the basis for your new GUI. You could copy over your display code from project 1, but you need to make sure and remove any code you don't need for project 3. It's probably better to use the new skeleton file and then copy over any specific functions you need from the old file.
The first step is to create a set of axes in your visualization
program. Import your View class into your application and make a new
View object in your application's __init__ method.
A simple method of implementing axes is to create and store a numpy matrix with the axis endpoints. You should have six endpoints, two for each axis, and the length of each axis should be 1. Each axis should have one endpoint at the origin and the other a distance 1 along its corrsponding axis. So the X-axis endpoints should be [0, 0, 0, 1], and [1, 0, 0, 1] (with the homogenous coordinate).
You'll also want a field to hold a list that contains the actual graphics line objects that instantiate them on the screen. Initialize these variables in your __init__ to appropriate values (but don't actually create the lines on the canvas there).
Make a function (e.g. buildAxes()) that builds the view transformation matrix [VTM], multiplies the axis endpoints by the VTM, then creates three new line objects, one for each axis. Use the x and y coordinates of the transformed endpoints as the pixel locations for the lines.
Note that the VTM is built assuming that the points are in columns. If you are representing the axis points as a set of rows in a matrix, you will need to do the following.
pts = (vtm * self.axes.T).T
The above transposes the axis points so each point is a column, multiplies it by the VTM, and then takes the transpose so that the pts matrix has the axis endpoints as rows again. Whatever you do, be consistent in your representations.
Make another function (e.g. updateAxes()) that executes the following algorithm.
# build the VTM # multiply the axis endpoints by the VTM # for each line object # update the coordinates of the object
Note that the original axis endpoints do not, in general, ever change for a given visualization. Only the VTM changes, which causes the screen appearance of the axes to change. If you normalize your data sets prior to running them through the view pipeline, which you should do, then axes of unit length should be appropriate.
See if your axes make sense when you view them.
User Interaction: The final part of the lab is to add user interaction
to control the axes.
Bind a function (eg. handleButton1) to the button 1 motion event. The
standard button 1 event should store the user's click into a variable
(e.g. baseClick1). The button 1 motion function should implement the
# Calculate the differential motion since the last time the function was called # Divide the differential motion (dx, dy) by the screen size (view X, view Y) # Multiply the horizontal and vertical motion by the horizontal and vertical extents. # Put the result in delta0 and delta1 # The VRP should be updated by delta0 * U + delta1 * VUP (this is a vector equation) # call updateAxes()
Test your translation. See what happens if you put some multipliers on delta0 and delta1 to slow down or speed up the motion.
Button 3 motion should implement scaling. The scaling behavior should
act like a vertical lever. The button 3 click should store a base
click point that does not change while the user holds down the mouse
button. It should also store the value of the extent in the view
space when the user clicked. This is the original extent. Make sure
you create a copy and not a reference to the extent value (you could
use the View clone function).
The button 3 motion should convert the distance between the base click and the current mouse position into a scale factor. Keep the scale factor between 0.1 and 3.0. You can then multiply the original extent by the factor and put it into the View object. Then call updateAxes(). Do not modify the original extent while the mouse is in motion.
Test out this capability. If you click in the window, as you move above your original click point, the scene should zoom in. As you move below your original click point, the scene should zoom out. As you come back to your original click point, the scene should go back to its original scale.
Pick a rotation method. You could choose to rotate about the VRP.
The method described below rotates about the center of the view
Make a method in your View class called rotateVRC that takes two angles as arguments, in addition to self. The two angles are how much to rotate about the VUP axis and how much to rotate about the U axis. The process you want to follow is to translate the center of rotation (the middle of the extent volume) to the origin, rotate around the Y axis, rotate around the X axis, then translate back by the opposite of the first translation.
The process is as follows.
- Make a translation matrix to move the point ( VRP + VPN * extent[Z] * 0.5 ) to the origin. Put it in t1.
- Make an axis alignment matrix Rxyz using u, vup and vpn.
- Make a rotation matrix about the Y axis by the VUP angle, put it in r1.
- Make a rotation matrix about the X axis by the U angle. Put it in r2.
- Make a translation matrix that has the opposite translation from step 1.
- Make a numpy matrix where the VRP is on the first row, with a 1 in the homogeneous coordinate, and u, vup, and vpn are the next three rows, with a 0 in the homogeneous coordinate.
Execute the following: tvrc = (t2*Rxyz.T*r2*r1*Rxyz*t1*tvrc.T).T
Then copy the values from tvrc back into the VPR, U, VUP, and VPN fields and normalize U, VUP, and VPN.
Add a button 2 motion function and binding. The button 2 click should
store the button click location into a variable like baseClick2. The
same function should store a clone of your View object. This is the
original View object. In addition to Button-2 and B2-Motion, bind
Control-Button-1 and Control-B1-motion to the button 2 functionality.
You may not be able to access the Button-2 event on a laptop without a
Within the button2motion function, first calculate delta0 and delta1 as the pixel motion differences in x and y divided by a constant (e.g. 200) and multiplied by pi (math.pi). Think of it as how many pixels the user must move the mouse to execute a 180 degree rotation.
Clone the original View object and assign it to your standard view field, then rotate the view using the rotateVRC method and the two angles delta0 and delta1. Then call updateAxes.
See if your function does the right thing. You may need to negate the delta1 (vertical motion) to get the proper behavior.
- Bind a function (eg. handleButton1) to the button 1 motion event. The standard button 1 event should store the user's click into a variable (e.g. baseClick1). The button 1 motion function should implement the following algorithm.
- Add data to your visualization. If you wish, start with this data set in which all the values are between 0 and 1.
- Give the user control over the interaction constants so they can control the speed of translation, scaling, or rotation.
- Add labels to your axis that move along with the lines.
- Give visual cues to the user about how much they are scaled in/out.
- Give visual cues to the user about how they are oriented.
- Make a mini-window/map that shows the user what part of the data set they can currently see in the main window.
- Complete the handleReset function to add a reset capability that brings the user back to the default view.
- Add interface elements to control the user interface. For example, add hot keys to put the view in particular viewing conditions (e.g. x-y plane / x-z plane / y-z plane).
Make a wiki page for the project writeup.
Once you have written up your project, give the page the label:
Put your code in a project 3 folder in your private subdirectory in your CS251 folder on Courses.