Due midnight, 6 March 2013
The goal of this week's lab is to build a class for creating and managing the viewing parameters and view transformation matrix.
The main result of your work this week should be a View class that holds the current viewing parameters and can build a view transformation matrix [VTM] based on the parameters.
Create a file view.py and begin a class View (you can use any name you
like, but I'll provide some short test programs that assume the class
is named View).
Within the __init__ method, create the following fields and give them the specified default values. You may want to have a reset method, called from __init__ that assigns the default values, as you may want to enable the user to reset the view at any time. Note that the view parameters provide a complete view of the data, looking normal to the X-Y plane, assuming the data is all located inside the unit cube with one corner on the origin.
- vrp: a NumPy matrix with the default value [0.5, 0.5, 1].
- vpn: a NumPy matrix with the default value [0, 0, -1].
- vup: a NumPy matrix with the default value [0, 1, 0].
- u: a NumPy matrix with the default value [1, 0, 0].
- extent: a list or NumPy matrix with the default value [1, 1, 1]
- screen: a list or NumPy matrix with the default value [400, 400]
- offset: a list or NumPy matrix with the default value [20, 20]
Write a build method that that uses the current viewing
parameters to return a view matrix.
Generate a 4x4 identity matrix, which will be the basis for the view
matrix. For example:
vtm = numpy.identity( 4, float )
Generate a translation matrix to move the VRP to the origin and then
premultiply m by the translation matrix. For example:
t1 = numpy.matrix( [[1, 0, 0, -self.vrp[0, 0]], [0, 1, 0, -self.vrp[0, 1]], [0, 0, 1, -self.vrp[0, 2]], [0, 0, 0, 1] ] ) vtm = t1 * vtm
Calculate the view reference axes tu, tvup, tvpn.
- tu is the cross product (np.cross) of the vup and vpn vectors.
- tvup is the cross product of the vpn and tu vectors.
- tvpn is a copy of the vpn vector.
Normalize the view axes tu, tvup, and tvpn to unit length. You
probably want to write a simple normalize function to handle this.
Make sure you do not include the homogeneous coordinate in the
normalization process. A normalized vector has unit length, and can
be computed as below. (There are more compact ways to do this using
Length = math.sqrt( Vx*Vx + Vy*Vy + Vz*Vz )
Vnorm = Vx / length
Vnorm = Vy / length
Vnorm = Vz / length
- Copy the orthonormal axes tu, tvup, and tvpn back to self.u, self.vup and self.vpn.
Use the normalized view reference axes to generate the rotation matrix
to align the view reference axes and then premultiply M by the
rotation. For example:
# align the axes r1 = numpy.matrix( [[ tu[0, 0], tu[0, 1], tu[0, 2], 0.0 ], [ tvup[0, 0], tvup[0, 1], tvup[0, 2], 0.0 ], [ tvpn[0, 0], tvpn[0, 1], tvpn[0, 2], 0.0 ], [ 0.0, 0.0, 0.0, 1.0 ] ] ); vtm = r1 * vtm
Translate the lower left corner of the view space to the origin. Since
the axes are aligned, this is just a translation by half the extent of
the view volume in the X and Y view axes.
Note that the rest of the project description will use shorthand, rather than writing out the matrices explicitly. Conceptually, the translation is represented as:
vtm = T( 0.5*extent, 0.5*extent, 0 ) * vtm
Use the extent and screen size values to scale to the screen.
vtm = S( -screen / extent, -screen / extent, 1.0 / extent ) * m
Finally, translate the lower left corner to the origin and add the
view offset, which gives a little buffer around the top and left edges
of the window.
m = T( screen + offset, screen + offset, 0 ) * m
If your code is working properly, then using the default parameters you should get the matrix below.
[[ 400. 0. 0. 20.] [ 0. -400. 0. 420.] [ 0. 0. -1. 1] [ 0. 0. 0. 1.]]
- Generate a 4x4 identity matrix, which will be the basis for the view matrix. For example:
A simple method of implementing axes is to create and store a numpy matrix with the axis endpoints. You should have six endpoints, and the length of each axis should be 1 (don't assume that one of the endpoints will always be zero). You'll also want a list to hold the actual graphics objects (the lines) that instantiate them on the screen. Initialize these variables to None in your __init__.
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. Store the axis endpoints and the three line objects.
Note that the VTM is 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.
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, 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.
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 and 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.
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().
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 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.
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, then rotate it using the rotateVRC method and the two angles delta0 and delta1. Then call updateAxes. /p>
See if your function does the right thing. You may need to negate the delta1 (vertical motion) to get the proper behavior.
- 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.
- 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.
Make a wiki page for the project writeup. On it, describe your View class API, with brief descriptions of all the functions, their inputs, outputs, and purpose.
Describe in your writeup how you store the parameters internally in your View class.
Include in your writeup a screen capture of a 3D data set in two different viewing situations.
Once you have written up your assignment, give the page the label:
Put your code in a zip file in your private subdirectory in the COMP/CS251 folder on Courses.