Animating Graphic Objects using Python

0
315
9 min read

Python 2.6 Graphics Cookbook

Python 2.6 Graphics Cookbook

Over 100 great recipes for creating and animating graphics using Python

  • Create captivating graphics with ease and bring them to life using Python
  • Apply effects to your graphics using powerful Python methods
  • Develop vector as well as raster graphics and combine them to create wonders in the animation world
  • Create interactive GUIs to make your creation of graphics simpler
  • Part of Packt’s Cookbook series: Each recipe is a carefully organized sequence of instructions to accomplish the task of creation and animation of graphics as efficiently as possible       

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.

Python 2.6 Graphics Cookbook

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.

Python 2.6 Graphics Cookbook

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


Subscribe to the weekly Packt Hub newsletter

* indicates required

LEAVE A REPLY

Please enter your comment!
Please enter your name here