Due: , 11:59 pm
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 graphics.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.
The Zelle graphics GraphWin object contains four methods that let you access user input. We've used getMouse() and checkMouse(), which wait for a mouse click (getMouse()) or check if one has occurred recently (checkMouse()).
The GraphWin object also contains methods for accessing keystrokes. The getKey() method waits for the user to press a key, while the checkKey() returns either the empty string '' or the character most recently used.
Make a new file, input.py, that imports graphics. Write a main function that creates a window, then enters a while loop. Inside the while loop, have your program print out the return value of checkMouse() if it is not None and the return value of checkKey() if it is not the empty string. Then click and type and see what comes up. Think about how you could use this capability in the current project.
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.
Create a new file, rotation.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.
Start a new class called RotatingLine. For the lab exercise, we will not have it inherit from the Thing class. 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 anchor point will be defined by 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.
| pos | The x0 and y0 values as a 2-element list. |
| length | The length of the line. |
| anchor | the Ax and Ay values as a 2-element list, if both are given, otherwise x0, y0 (this involves an if statement) |
| points | A list that holds two 2-element lists (details below). |
| angle | The current orientation of the line. Initialize it to 0.0. |
| rvel | Rotational velocity (in degrees/s). Initialize it to 0.0. |
| win | A GraphWin object reference. |
| scale | The scale factor from model coordinates to screen coordinates. |
| vis | A list to hold the graphics Line object. Set it to the empty list, for now. |
| drawn | A 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.
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.
For each of the 2D vertices in the self.points list, we need to execute the following actions.
Once the loop is done, you will have a list 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's 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.
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 Zelle graphics 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 two Point objects in pts
The next step is to write a draw() method for the RotatingLine class. The draw() method has three steps. First, execute a for loop over self.vis and have each element undraw itself. Then call the render() method. Then execute a for loop over self.vis and have each element draw itself into the stored window (self.win). Finally, set the field drawn to True.
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, then if the value of self.drawn is True, it should call self.draw().
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)
line.draw()
while win.checkMouse() == None:
line.setAngle( line.getAngle() + 3)
time.sleep(0.08)
win.update()
win.getMouse()
win.close()
if __name__ == "__main__":
test1()
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, if the line is drawn (i.e. if self.drawn is True), call the draw() 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. Then put the call self.setAnchor( 20, 25 ) before
the setAngle() call. 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 start on the rest of the project.
© 2018 Caitrin Eaton.