CS 251: Assignment #3

Viewing

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.


Tasks

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.

  1. 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]

  2. Write a build method that that uses the current viewing parameters to return a view matrix.
    1. Generate a 4x4 identity matrix, which will be the basis for the view matrix. For example:

      vtm = numpy.identity( 4, float )

    2. 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
      
    3. Calculate the view reference axes tu, tvup, tvpn.
      1. tu is the cross product (np.cross) of the vup and vpn vectors.
      2. tvup is the cross product of the vpn and tu vectors.
      3. tvpn is a copy of the vpn vector.
    4. 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 numpy.)

      Length = math.sqrt( Vx*Vx + Vy*Vy + Vz*Vz )
      Vnorm[0] = Vx / length
      Vnorm[1] = Vy / length
      Vnorm[2] = Vz / length

    5. Copy the orthonormal axes tu, tvup, and tvpn back to self.u, self.vup and self.vpn.
    6. 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
      
    7. 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], 0.5*extent[1], 0 ) * vtm

    8. Use the extent and screen size values to scale to the screen.

      vtm = S( -screen[0] / extent[0], -screen[1] / extent[1], 1.0 / extent[2] ) * m

    9. 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[0] + offset[0], screen[1] + offset[1], 0 ) * m

    10. 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.]]
      
  3. Create a clone method for your View object that makes a duplicate View object and returns it.
  4. The next 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__.

    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.

  5. User Interaction: The final part of the lab is to add user interaction to control the axes.
    1. 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.

      # 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.

    2. 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.

    3. Pick a rotation method. You could choose to rotate about the VRP. The method described below rotates about the center of the view volume.

      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.

      1. Make a translation matrix to move the point ( VRP + VPN * extent[Z] * 0.5 ) to the origin. Put it in t1.
      2. Make an axis alignment matrix Rxyz using u, vup and vpn.
      3. Make a rotation matrix about the Y axis by the VUP angle, put it in r1.
      4. Make a rotation matrix about the X axis by the U angle. Put it in r2.
      5. Make a translation matrix that has the opposite translation from step 1.
      6. 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.
      7. 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.

    4. 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.


Extensions


Writeup

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.

Handin

Once you have written up your assignment, give the page the label:

cs251s13project3

Put your code in a zip file in your private subdirectory in the COMP/CS251 folder on Courses.