## Python 2.6 Graphics Cookbook

Read more about this book |

*(For more resources on Python, see here.)*

# Precise collisions using floating point numbers

Here the simulation flaws caused by the coarseness of integer arithmetic are eliminated by using floating point numbers for all ball position calculations.

## How to do it...

All position, velocity, and gravity variables are made floating point by writing them with explicit decimal points. The result is shown in the following screenshot, showing the bouncing balls with trajectory tracing.

from Tkinter import *

root = Tk()

root.title("Collisions with Floating point")

cw = 350 # canvas width

ch = 200 # canvas height

GRAVITY = 1.5

chart_1 = Canvas(root, width=cw, height=ch, background="black")

chart_1.grid(row=0, column=0)

cycle_period = 80 # Time between new positions of the ball

# (milliseconds).

time_scaling = 0.2 # This governs the size of the differential steps

# when calculating changes in position.

# The parameters determining the dimensions of the ball and it's

# position.

ball_1 = {'posn_x':25.0, # x position of box containing the

# ball (bottom).

'posn_y':180.0, # x position of box containing the

# ball (left edge).

'velocity_x':30.0, # amount of x-movement each cycle of

# the 'for' loop.

'velocity_y':100.0, # amount of y-movement each cycle of

# the 'for' loop.

'ball_width':20.0, # size of ball - width (x-dimension).

'ball_height':20.0, # size of ball - height (y-dimension).

'color':"dark orange", # color of the ball

'coef_restitution':0.90} # proportion of elastic energy

# recovered each bounce

ball_2 = {'posn_x':cw - 25.0,

'posn_y':300.0,

'velocity_x':-50.0,

'velocity_y':150.0,

'ball_width':30.0,

'ball_height':30.0,

'color':"yellow3",

'coef_restitution':0.90}

def detectWallCollision(ball):

# Collision detection with the walls of the container

if ball['posn_x'] > cw - ball['ball_width']: # Collision

# with right-hand wall.

ball['velocity_x'] = -ball['velocity_x'] * ball['coef_ \

restitution'] # reverse direction.

ball['posn_x'] = cw - ball['ball_width']

if ball['posn_x'] < 1: # Collision with left-hand wall.

ball['velocity_x'] = -ball['velocity_x'] * ball['coef_ \restitution']

ball['posn_x'] = 2 # anti-stick to the wall

if ball['posn_y'] < ball['ball_height'] : # Collision

# with ceiling.

ball['velocity_y'] = -ball['velocity_y'] * ball['coef_ \ restitution']

ball['posn_y'] = ball['ball_height']

if ball['posn_y'] > ch - ball['ball_height']: # Floor

# collision.

ball['velocity_y'] = - ball['velocity_y'] * ball['coef_ \restitution']

ball['posn_y'] = ch - ball['ball_height']

def diffEquation(ball):

# An approximate set of differential equations of motion

# for the balls

ball['posn_x'] += ball['velocity_x'] * time_scaling

ball['velocity_y'] = ball['velocity_y'] + GRAVITY # a crude

# equation incorporating gravity.

ball['posn_y'] += ball['velocity_y'] * time_scaling

chart_1.create_oval( ball['posn_x'], ball['posn_y'], ball['posn_x'] + ball['ball_width'],\

ball ['posn_y'] + ball['ball_height'], \

fill= ball['color'])

detectWallCollision(ball) # Has the ball collided with

# any container wall?

for i in range(1,2000): # end the program after 1000 position shifts.

diffEquation(ball_1)

diffEquation(ball_2)

chart_1.update() # This refreshes the drawing on the canvas.

chart_1.after(cycle_period) # This makes execution pause for 200

# milliseconds.

chart_1.delete(ALL) # This erases everything on the

root.mainloop()

## How it works...

Use of precision arithmetic has allowed us to notice simulation behavior that was previously hidden by the sins of integer-only calculations. This is the UNIQUE VALUE OF GRAPHIC SIMULATION AS A DEBUGGING TOOL. If you can represent your ideas in a visual way rather than as lists of numbers you will easily pick up subtle quirks in your code. The human brain is designed to function best in graphical images. It is a direct consequence of being a hunter.

## A graphic debugging tool...

There is another very handy trick in the software debugger's arsenal and that is the visual trace. A trace is some kind of visual trail that shows the history of dynamic behavior. All of this is revealed in the next example.

# Trajectory tracing and ball-to-ball collisions

Now we introduce one of the more difficult behaviors in our simulation of ever increasing complexity – the mid-air collision.

The hardest thing when you are debugging a program is to try to hold in your short term memory some recently observed behavior and compare it meaningfully with present behavior. This kind of memory is an imperfect recorder. The way to overcome this is to create a graphic form of memory – some sort of picture that shows accurately what has been happening in the past. In the same way that military cannon aimers use glowing tracer projectiles to adjust their aim, a graphic programmer can use trajectory traces to examine the history of execution.

## How to do it...

In our new code there is a new function called *detect_ball_collision (ball_1, ball_2)* whose job is to anticipate imminent collisions between the two balls no matter where they are. The collisions will come from any direction and therefore we need to be able to test all possible collision scenarios and examine the behavior of each one and see if it does not work as planned. This can be too difficult unless we create tools to test the outcome. In this recipe, the tool for testing outcomes is a graphic trajectory trace. It is a line that trails behind the path of the ball and shows exactly where it went right since the beginning of the simulation. The result is shown in the following screenshot, showing the bouncing with ball-to-ball collision rebounds.

# kinetic_gravity_balls_1.py

# >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

from Tkinter import *

import math

root = Tk()

root.title("Balls bounce off each other")

cw = 300 # canvas width

ch = 200 # canvas height

GRAVITY = 1.5

chart_1 = Canvas(root, width=cw, height=ch, background="white")

chart_1.grid(row=0, column=0)

cycle_period = 80 # Time between new positions of the ball

# (milliseconds).

time_scaling = 0.2 # The size of the differential steps

# The parameters determining the dimensions of the ball and its

# position.

ball_1 = {'posn_x':25.0,

'posn_y':25.0,

'velocity_x':65.0,

'velocity_y':50.0,

'ball_width':20.0,

'ball_height':20.0,

'color':"SlateBlue1",

'coef_restitution':0.90}

ball_2 = {'posn_x':180.0,

'posn_y':ch- 25.0,

'velocity_x':-50.0,

'velocity_y':-70.0,

'ball_width':30.0,

'ball_height':30.0,

'color':"maroon1",

'coef_restitution':0.90}

def detect_wall_collision(ball):

# detect ball-to-wall collision

if ball['posn_x'] > cw - ball['ball_width']: # Right-hand wall.

ball['velocity_x'] = -ball['velocity_x'] * ball['coef_ \restitution']

ball['posn_x'] = cw - ball['ball_width']

if ball['posn_x'] < 1: # Left-hand wall.

ball['velocity_x'] = -ball['velocity_x'] * ball['coef_ \restitution']

ball['posn_x'] = 2

if ball['posn_y'] < ball['ball_height'] : # Ceiling.

ball['velocity_y'] = -ball['velocity_y'] * ball['coef_ \restitution']

ball['posn_y'] = ball['ball_height']

if ball['posn_y'] > ch - ball['ball_height'] : # Floor

ball['velocity_y'] = - ball['velocity_y'] * ball['coef_ \restitution']

ball['posn_y'] = ch - ball['ball_height']

def detect_ball_collision(ball_1, ball_2):

#detect ball-to-ball collision

# firstly: is there a close approach in the horizontal direction

if math.fabs(ball_1['posn_x'] - ball_2['posn_x']) < 25:

# secondly: is there also a close approach in the vertical

# direction.

if math.fabs(ball_1['posn_y'] - ball_2['posn_y']) < 25:

ball_1['velocity_x'] = -ball_1['velocity_x'] # reverse

# direction.

ball_1['velocity_y'] = -ball_1['velocity_y']

ball_2['velocity_x'] = -ball_2['velocity_x']

ball_2['velocity_y'] = -ball_2['velocity_y']

# to avoid internal rebounding inside balls

ball_1['posn_x'] += ball_1['velocity_x'] * time_scaling

ball_1['posn_y'] += ball_1['velocity_y'] * time_scaling

ball_2['posn_x'] += ball_2['velocity_x'] * time_scaling

ball_2['posn_y'] += ball_2['velocity_y'] * time_scaling

def diff_equation(ball):

x_old = ball['posn_x']

y_old = ball['posn_y']

ball['posn_x'] += ball['velocity_x'] * time_scaling

ball['velocity_y'] = ball['velocity_y'] + GRAVITY

ball['posn_y'] += ball['velocity_y'] * time_scaling

chart_1.create_oval( ball['posn_x'], ball['posn_y'],\

ball['posn_x'] + ball['ball_width'],\

ball['posn_y'] + ball['ball_height'],\

fill= ball['color'], tags="ball_tag")

chart_1.create_line( x_old, y_old, ball['posn_x'], \

ball ['posn_y'], fill= ball['color'])

detect_wall_collision(ball) # Has the ball

# collided with any container wall?

for i in range(1,5000):

diff_equation(ball_1)

diff_equation(ball_2)

detect_ball_collision(ball_1, ball_2)

chart_1.update()

chart_1.after(cycle_period)

chart_1.delete("ball_tag") # Erase the balls but

# leave the trajectories

root.mainloop()

## How it works...

Mid-air ball against ball collisions are done in two steps. In the first step, we test whether the two balls are close to each other inside a vertical strip defined by *if math.fabs(ball_1['posn_x'] - ball_2['posn_x']) < 25*. In plain English, this asks "Is the horizontal distance between the balls less than 25 pixels?" If the answer is yes, then the region of examination is narrowed down to a small vertical distance less than 25 pixels by the statement *if math.fabs(ball_1['posn_y'] - ball_2['posn_y']) < 25*. So every time the loop is executed, we sweep the entire canvas to see if the two balls are both inside an area where their bottom-left corners are closer than 25 pixels to each other. If they are that close then we simply cause a rebound off each other by reversing their direction of travel in both the horizontal and vertical directions.

## There's more...

Simply reversing the direction is not the mathematically correct way to reverse the direction of colliding balls. Certainly billiard balls do not behave that way. The law of physics that governs colliding spheres demands that momentum be conserved.

## Why do we sometimes get tkinter.TckErrors?

If we click the close window button (the X in the top right) while Python is paused, when Python revives and then calls on *Tcl* (Tkinter) to draw something on the canvas we will get an error message. What probably happens is that the application has already shut down, but *Tcl* has unfinished business. If we allow the program to run to completion before trying to shut the window then termination is orderly.

Read more about this book |

*(For more resources on Python, see here.)*

# Rotating line

Now we will see how to handle rotating lines. In any kind of graphic computer work, the need to rotate objects arises eventually. By starting off as simply as possible and progressively adding behaviors we can handle some increasingly complicated situations. This recipe is that first simple step in the art of making things rotate.

## Getting ready

To understand the mathematics of rotation you need to be reasonably familiar with the trigonometry functions of sine, cosine, and tangent. The good news for those of us whose eyes glaze at the mention of trigonometry is that you can use these examples without understanding trigonometry. However, it is much more rewarding if you do try to figure out the math. It is like the difference between watching football or playing it. Only the players get fit.

## How to do it...

You just need to write and run this code and observe the results as you did for all the other recipes. The insights come from repeated tinkering and hacking the code. Change the values of variables *p1_x* to *p2_y* one at a time and observe the results.

# rotate_line_1.py

#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

from Tkinter import *

import math

root = Tk()

root.title("Rotating line")

cw = 220 # canvas width

ch = 180 # canvas height

chart_1 = Canvas(root, width=cw, height=ch, background="white")

chart_1.grid(row=0, column=0)

cycle_period = 50 # pause duration (milliseconds).

p1_x = 90.0 # the pivot point

p1_y = 90.0 # the pivot point,

p2_x = 180.0 # the specific point to be rotated

p2_y = 160.0 # the specific point to be rotated.

a_radian = math.atan((p2_y - p1_y)/(p2_x - p1_x))

a_length = math.sqrt((p2_y - p1_y)*(p2_y - p1_y) +\

(p2_x - p1_x)*(p2_x - p1_x))

for i in range(1,300): # end the program after 300 position shifts

a_radian +=0.05 # incremental rotation of 0.05 radians

p1_x = p2_x - a_length * math.cos(a_radian)

p1_y = p2_y - a_length * math.sin(a_radian)

chart_1.create_line(p1_x, p1_y, p2_x, p2_y)

chart_1.update()

chart_1.after(cycle_period)

chart_1.delete(ALL)

root.mainloop()

## How it works...

In essence, all rotation comes down to the following:

- Establish a center of rotation or pivot point
- Pick a specific point on the object you want to rotate
- Calculate the distance from the pivot point to the specific point of interest
- Calculate the angle of the line joining the pivot and the specific point
- Increase the angle of the line joining the points by a known amount, the rotation angle, and re-calculate the new x and y coordinates for that point.

For math students what you do is relocate the origin of your rectangular coordinate system to the pivot point, express the coordinates of your specific point into polar coordinates, add an increment to the angular position, and convert the new polar coordinate position into a fresh pair of rectangular coordinates. The preceding recipe performs all these actions.

## There's more...

The pivot point was purposely placed near the bottom corner of the canvas so that the point on the end of the line to be rotated would fall outside the canvas for much of the rotation process. The rotation continues without errors or bad behavior emphasizing a point made earlier that Python is mathematically robust. However, we need to exercise care when using the *arctangent* function *math.atan()* because it flips from a value positive infinity to negative infinity as angles move through 90 and 270 degrees. *Atan()* can give ambiguous results. Again the Python designers have taken care of business well by creating the math. *atan2(y,x)* function that takes into account the signs of both y and x to give unambiguous results between 180 degrees and -180.

# Trajectory tracing on multiple line rotations

This example draws a visually appealing kind of Art Noveau arrowhead but that is just an issue on the happy-side. The real point of this recipe is to see how you can have any number of pivot points all with different motions and that the essential arithmetic remains simple and clean looking in Python. The use of animation methods to slow the execution down makes it entertaining to watch. We also see how tag names given to different parts of the objects drawn onto the canvas allow them to be selectively erased when the *canvas.delete(...)* method is invoked.

## Getting ready

Imagine a skilled drum major marching in a parade whirling a staff in circles. Holding onto the end of the staff is a small monkey also twirling a baton but at a different speed. At the tip of the monkey's staff is a miniature marmoset twirling a baton in the opposite direction...

Now run the program.

## How to do it...

Run the Python code below as we have done before. The result is shown in following screenshot showing multiple line rotation traces.

# multiple_line_rotations_1.py

#>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

from Tkinter import *

import math

root = Tk()

root.title("multi-line rotations")

cw = 600 # canvas width

ch = 600 # canvas height

chart_1 = Canvas(root, width=cw, height=ch, background="white")

chart_1.grid(row=0, column=0)

cycle_period = 50 # time between new positions of the ball

# (milliseconds).

p0_x = 300.0

p0_y = 300.0

p1_x = 200.0

p1_y = 200.0

p2_x = 150.0 # central pivot

p2_y = 150.0 # central pivot

p3_x = 100.0

p3_y = 100.0

p4_x = 50.0

p4_y = 50.0

alpha_0 = math.atan((p0_y - p1_y)/(p0_x - p1_x))

length_0_1 = math.sqrt((p0_y - p1_y)*(p0_y - p1_y) +\

(p0_x - p1_x)*(p0_x - p1_x))

alpha_1 = math.atan((p1_y - p2_y)/(p1_x - p2_x))

length_1_2 = math.sqrt((p2_y - p1_y)*(p2_y - p1_y) +\

(p2_x - p1_x)*(p2_x - p1_x))

alpha_2 = math.atan((p2_y - p3_y)/(p2_x - p3_x))

length_2_3 = math.sqrt((p3_y - p2_y)*(p3_y - p2_y) +\

(p3_x - p2_x)*(p3_x - p2_x))

alpha_3 = math.atan((p3_y - p4_y)/(p3_x - p4_x))

length_3_4 = math.sqrt((p4_y - p3_y)*(p4_y - p3_y) +\

(p4_x - p3_x)*(p4_x - p3_x))

for i in range(1,5000):

alpha_0 += 0.1

alpha_1 += 0.3

alpha_2 -= 0.4

p1_x = p0_x - length_0_1 * math.cos(alpha_0)

p1_y = p0_y - length_0_1 * math.sin(alpha_0)

tip_locus_2_x = p2_x

tip_locus_2_y = p2_y

p2_x = p1_x - length_1_2 * math.cos(alpha_1)

p2_y = p1_y - length_1_2 * math.sin(alpha_1)

tip_locus_3_x = p3_x

tip_locus_3_y = p3_y

p3_x = p2_x - length_2_3 * math.cos(alpha_2)

p3_y = p2_y - length_2_3 * math.sin(alpha_2)

tip_locus_4_x = p4_x

tip_locus_4_y = p4_y

p4_x = p3_x - length_3_4 * math.cos(alpha_3)

p4_y = p3_y - length_3_4 * math.sin(alpha_3)

chart_1.create_line(p1_x, p1_y, p0_x, p0_y, tag='line_1')

chart_1.create_line(p2_x, p2_y, p1_x, p1_y, tag='line_2')

chart_1.create_line(p3_x, p3_y, p2_x, p2_y, tag='line_3')

chart_1.create_line(p4_x, p4_y, p3_x, p3_y, fill="purple", \tag='line_4')

# Locus tip_locus_2 at tip of line 1-2

chart_1.create_line(tip_locus_2_x, tip_locus_2_y, p2_x, p2_y, \ fill='maroon')

# Locus tip_locus_2 at tip of line 2-3

chart_1.create_line(tip_locus_3_x, tip_locus_3_y, p3_x, p3_y, \ fill='orchid1')

# Locus tip_locus_2 at tip of line 2-3

chart_1.create_line(tip_locus_4_x, tip_locus_4_y, p4_x, p4_y, \ fill='DeepPink')

chart_1.update()

chart_1.after(cycle_period)

chart_1.delete('line_1', 'line_2', 'line_3')

root.mainloop()

## How it works...

As we did in the previous recipe we have lines defined by connecting two points, each being specified in the rectangular coordinates that Tkinter drawing methods use. There are three such lines connected pivot-to-tip. It may help to visualize each pivot as a drum major or a monkey. We convert each pivot-to-tip line into polar coordinates of length and angle. Then each pivot-to-tip line is rotated by its own individual increment angle. If you alter these angles alpha_1 etc. or the positions of the various pivot points you will get a limitless variety of interesting patterns.

## There's more...

Once you are able to control and vary color you are able to make extraordinary and beautiful patterns never seen before.

# A rose for you

This example is simply a gift for the reader. No illustration is provided. We will only see the result if we run the code. It is a surprise.

from Tkinter import *

root = Tk()

root.title("This is for you dear reader. A token of esteem and affection.")

import math

cw = 800 # canvas width

ch = 800 # canvas height

chart_1 = Canvas(root, width=cw, height=ch, background="black")

chart_1.grid(row=0, column=0)

p0_x = 400.0

p0_y = 400.0

p1_x = 330.0

p1_y = 330.0

p2_x = 250.0

p2_y = 250.0

p3_x = 260.0

p3_y = 260.0

p4_x = 250.0

p4_y = 250.0

p5_x = 180.0

p5_y = 180.0

alpha_0 = math.atan((p0_y - p1_y)/(p0_x - p1_x))

length_0_1 = math.sqrt((p0_y - p1_y)*(p0_y - p1_y) + (p0_x - p1_ \x)*(p0_x - p1_x))

alpha_1 = math.atan((p1_y - p2_y)/(p1_x - p2_x))

length_1_2 = math.sqrt((p2_y - p1_y)*(p2_y - p1_y) + (p2_x - p1_ \x)*(p2_x - p1_x))

alpha_2 = math.atan((p2_y - p3_y)/(p2_x - p3_x))

length_2_3 = math.sqrt((p3_y - p2_y)*(p3_y - p2_y) + (p3_x - p2_ \ x)*(p3_x - p2_x))

alpha_3 = math.atan((p3_y - p4_y)/(p3_x - p4_x))

length_3_4 = math.sqrt((p4_y - p3_y)*(p4_y - p3_y) + (p4_x - p3_ \ x)*(p4_x - p3_x))

alpha_4 = math.atan((p3_y - p5_y)/(p3_x - p5_x))

length_4_5 = math.sqrt((p5_y - p4_y)*(p5_y - p4_y) + (p5_x - p4_ \x)*(p5_x - p4_x))

for i in range(1,2300): # end the program after 500 position

# shifts.

alpha_0 += 0.003

alpha_1 += 0.018

alpha_2 -= 0.054

alpha_3 -= 0.108

alpha_4 += 0.018

p1_x = p0_x - length_0_1 * math.cos(alpha_0)

p1_y = p0_y - length_0_1 * math.sin(alpha_0)

tip_locus_2_x = p2_x

tip_locus_2_y = p2_y

p2_x = p1_x - length_1_2 * math.cos(alpha_1)

p2_y = p1_y - length_1_2 * math.sin(alpha_1)

tip_locus_3_x = p3_x

tip_locus_3_y = p3_y

p3_x = p2_x - length_2_3 * math.cos(alpha_2)

p3_y = p2_y - length_2_3 * math.sin(alpha_2)

tip_locus_4_x = p4_x

tip_locus_4_y = p4_y

p4_x = p3_x - length_3_4 * math.cos(alpha_3)

p4_y = p3_y - length_3_4 * math.sin(alpha_3)

tip_locus_5_x = p5_x

tip_locus_5_y = p5_y

p5_x = p4_x - length_4_5 * math.cos(alpha_4)

p5_y = p4_y - length_4_5 * math.sin(alpha_4)

chart_1.create_line(p1_x, p1_y, p0_x, p0_y, tag='line_1', \ fill='gray')

chart_1.create_line(p2_x, p2_y, p1_x, p1_y, tag='line_2', \ fill='gray')

chart_1.create_line(p3_x, p3_y, p2_x, p2_y, tag='line_3', \ fill='gray')

chart_1.create_line(p4_x, p4_y, p3_x, p3_y, tag='line_4', \ fill='gray')

chart_1.create_line(p5_x, p5_y, p4_x, p4_y, tag='line_5', \ fill='#550000')

chart_1.create_line(tip_locus_2_x, tip_locus_2_y, p2_x, p2_y, \ fill='#ff00aa')

chart_1.create_line(tip_locus_3_x, tip_locus_3_y, p3_x, p3_y, \ fill='#aa00aa')

chart_1.create_line(tip_locus_4_x, tip_locus_4_y, p4_x, p4_y, \ fill='#dd00dd')

chart_1.create_line(tip_locus_5_x, tip_locus_5_y, p5_x, p5_y, \ fill='#880066')

chart_1.create_line(tip_locus_2_x, tip_locus_2_y, p5_x, p5_y, \ fill='#0000ff')

chart_1.create_line(tip_locus_3_x, tip_locus_3_y, p4_x, p4_y, \ fill='#6600ff')

chart_1.update() # This refreshes the drawing on the

# canvas.

chart_1.delete('line_1', 'line_2', 'line_3', 'line_4') # Erase

# selected tags.

root.mainloop()

## How it works...

The structure of this program is similar to the previous example but the rotation parameters have been adjusted to evoke the image of a rose. The colors used are chosen to remind us that control over color is extremely import in graphics.

# Summary

In this article we covered the following recipes:

- Colliding balls with tracer trails
- Elastic ball against ball collisions
- Dynamic debugging
- Trajectory tracing
- Rotating a line and vital trigonometry
- Rotating lines which rotate lines
- A digital flower

**Further resources on this subject:**

- Python Graphics: Animation Principles [Article]
- Python: Unit Testing with Doctest [Article]
- Python Multimedia: Animations Examples using Pyglet [Article]
- Python 3 Object Oriented Programming: Managing objects [Article]
- Python 3 Object Oriented Programming [Book]