CS 152: Lab 10

Title image Project 10
Fall 2019

Lab Exercise 10: Geometric Thinking

This project continues our work with classes within the domain of physical simulations.

This is the thid part of a multi-part project where we will look at increasingly complex physical simulations. This week we add blocks that rotate.


If you have not already done so, mount your personal space, create a new project10 folder and bring up TextWrangler and a Terminal. As with last week, you will need the Zelle graphics package file graphicsPlus.py. You will also want to copy over your physics_objects.py from project 9. You will need a working Ball class for this lab exercise.

The Zelle documentation, is also available. Review the differences between the GraphWin methods checkMouse and getMouse, and between checkKey and getKey.

Quick Block Class Update

Update the __init__ method of your Block class so that is has default parameters named x0, y0, width, and height. Make sure to assign [x0, y0] to your position field and assign width and height to their corresponding fields. This is necessary because the rotating block collision code needs to create a Block and relies on the Block __init__ having those parameters.

Geometric Thinking and Rotation

No physics simulation is complete without the ability to rotate an object. In the real world 2D objects have three parameters (x, y, theta), where theta is the orientation of the object around the Z-axis (which points out of the screen). We're going to explore the geometric thinking required to make an object appear to rotate. We'll start with Line objects, but the same procedures apply to Polygon objects.

  1. Setup

    Create a new file, rot.py, in your project 10 directory. Put your name, date, etc. at the top and then import the graphics, math, and time packages.

    The goal of this exercise is to create a line object and then have it rotate 360 degrees around a user-selected point on the screen. We'll work with a scale factor of 10 (screen coordinates are 10x the size of model coordinates), but we will pretend that the Y-axis goes up, even though in screen coordinates it goes down.

  2. Create a RotatingLine class

    Start a new class called RotatingLine. For the lab exercise, we will not have it inherit from the Thing class (though we could). Start the __init__ method with the following definition.

    def __init__(self, win, x0, y0, length, Ax = None, Ay = None):

    The values x0 and y0 will be located at the center of the line. The length will be the total length of the line (1/2 to each side of the center point). The Ax and Ay values will be our rotation anchor point. By default, this will be the x0, y0 values at the center of the line. But we want to be able to rotate the line about any point of our choice.

    Create and assign the following fields in the RotatingLine __init__.

    posThe x0 and y0 values as a 2-element list.
    lengthThe length of the line.
    anchorthe Ax and Ay values as a 2-element list, if both are given, otherwise x0, y0 (this involves an if statement)
    pointsA list that holds two 2-element lists (details below).
    angleThe current orientation of the line. Initialize it to 0.0.
    rvelRotational velocity (in degrees/s). Initialize it to 0.0.
    winA GraphWin object reference.
    scaleThe scale factor from model coordinates to screen coordinates.
    visA list to hold the graphics Line object. Set it to the empty list, for now.
    drawnA Boolean variable to indicate if the Line has been drawn. Set it to False.

    For the points field, make a list that has two 2-element sub-lists. These sub-lists will hold the coordinates of the line as though it were centered at (0, 0) and stretched along the X-axis. Given those conditions, the endpoints of the line have to be (-length/2.0, 0.0) and (length/2.0, 0.0). Note the use of floating point values.

  3. Write a render method

    Next, create a method in the RotatingLine class called render. It should have self as the only argument. This function needs to make the appropriate Zelle Line object given the current center point, angle, and anchor point for the line. Remember, we want the line to appear like it's rotating around the anchor point.

    Create a variable pts to hold the Line endpoints. For each of the 2D vertices in the self.points list, execute the following actions.

    1. Add the vertex to the object's position and then subtract the anchor point, which puts the anchor point at the origin (0, 0).
    2. Rotate the vertices around the origin.
    3. Add the anchor point back to the vertices.
    4. Create a new Zelle Point object, taking into account scale and Y-axis orientation, and append it to the pts list.

    Once the loop is done, you will have a list (pts) with two Point objects from which you can create a Line object.

    Note that before starting the loop, you need to convert the value in self.angle from degrees to radians, as the math package sin and cos functions operate in radians. To convert a value A from degees to radians use the expression A*math.pi/180.0. Don't forget to import the math package.

    + (Outline)

      def render(self):
        # assign to theta the result of converting self.angle from degrees to radians
        # assign to cth the cosine of theta
        # assign to sth the sine of theta
        # assign to pts the empty list
        # for each vertex in self.points
          # (2 lines of code): assign to x and y the result of adding the vertex to self.pos and subtracting self.anchor
          # assign to xt the calculation x * cos(Theta) - y * sin(Theta) using your precomputed cos/sin values above
          # assign to yt the calculation x * sin(Theta) + y * cos(Theta)
          # (2 lines of code): assign to x and y the result of adding xt and yt to self.anchor
          # append to pts a Point object with coordinates (self.scale * x, self.win.getHeight() - self.scale*y)
        # assign to self.vis a list with a Zelle graphics Line object using the Point objects in pts
  4. Write a draw and an undraw method

    Write an undraw method that loops over the self.vis list and undraws each element. It should also set self.drawn to False. The draw method should loop and draw the elements, then set self.drawn to True.

    Design Note: If RotatingLine inherited Thing, it would not need to have its own draw/undraw functions.

  5. Write a refresh method for RotatingLine

    The next step is to write a refresh method for the RotatingLine class. The refresh method is similar to the Ball and Block classes: it recreates the visual representation based on the current state of the object. The difference is that the render method updates the vis list.

    1. Save the current value of self.drawn to a local variable (e.g. drawn)
    2. If the object is drawn (i.e. drawn is True), call self.undraw()
    3. Call the render method
    4. If the object is drawn, call self.draw(self.win)

    In the __init__ method, add a call to the refresh function as the last thing in the function.

  6. Write getAngle and setAngle functions

    Create two accessor functions: setAngle and getAngle. The getAngle function should return the current value of self.angle.

    The setAngle function should update the value of self.angle, but it also needs to redraw the line if it has already been drawn. After updating the value of self.angle, it should call the refresh method.

  7. Test the RotatingLine

    Once you have completed these steps, it's time to test. Use the following code (which is a function you should add to the file--not a method to add to the class--and run it. You should get a short line in the middle of the window that rotates.

    def test1():
        win = gr.GraphWin('line thingy', 500, 500, False)
        line = RotatingLine(win, 25, 25, 10)
        while win.checkMouse() == None:
            line.setAngle( line.getAngle() + 3)
    if __name__ == "__main__":
  8. Write a rotate method to orient the line

    Create a rotate method in the RotatingLine class. This should take in self and one parameter, which is the amount to rotate relative to the current orientation. This function is almost identical to the setAngle function, except that you want to increment the angle by the argument, not replace it. Then call the refresh method.

    Update your test function to use line.rotate() instead of line.setAngle() to modify the orientation of the line and try it again. The argument to line.rotate should be just the incremental amount to change the angle (e.g. 3).

    If you want to have the line rotate around a different point, create a setAnchor method that lets you specify the anchor point. (Or make use of the Ax, Ay parameters to the constructor.) Then put the call self.setAnchor( 20, 25 ) before the loop starts. When you run your test function, the line should appear to rotate around its left endpoint. Explore other anchor points and make sure this makes sense.

When you are done with the lab exercises, you may begin the project.