The physics system used in this simulation is both incomplete and incorrect. This recipe evolved from the Boids Simulation and was an early attempt to simulate bouncing balls in an area contained with a force field on the sides and a floor on the bottom. The spheres will respond to movement of the window and will bounce if accelerated upward quickly. This is primarily a recipe that I later built off of to create better software later on.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 | import random # FOR RANDOM BEGINNINGS
from Tkinter import * # ALL VISUAL EQUIPMENT
WIDTH = 400 # OF SCREEN IN PIXELS
HEIGHT = 400 # OF SCREEN IN PIXELS
BALLS = 7 # IN SIMULATION
WALL = 50 # FROM SIDE IN PIXELS
WALL_FORCE = 400 # ACCELERATION PER MOVE
SPEED_LIMIT = 3000 # FOR ball VELOCITY
BALL_RADIUS = 5 # FOR ballS IN PIXELS
OFFSET_START = 20 # FROM WALL IN PIXELS
FRAMES_PER_SEC = 40 # SCREEN UPDATE RATE
################################################################################
def main():
# Start the program.
initialise()
mainloop()
def initialise():
# Setup simulation variables.
global active
active = False
build_balls()
build_graph()
def build_graph():
# Build GUI environment.
global graph, left, top
root = Tk()
root.resizable(False, False)
root.title('Balls')
left = (root.winfo_screenwidth() - WIDTH) / 2
top = (root.winfo_screenheight() - HEIGHT) / 2
root.geometry('%dx%d+%d+%d' % (WIDTH, HEIGHT, left, top))
root.bind_all('<Escape>', lambda event: event.widget.quit())
root.bind('<Configure>', window_move)
graph = Canvas(root, width=WIDTH, height=HEIGHT, background='white')
graph.after(1000 / FRAMES_PER_SEC, update)
graph.after(1000, activate)
graph.pack()
def activate():
# Active window_move event.
global active
active = True
def window_move(event):
# Respond to movements.
global left, top
if active:
diff = TwoD(left - event.x, top - event.y)
for ball in balls:
if HEIGHT - WALL - 2 < ball.position.y and top > event.y:
ball.velocity.y -= (1000 * (top - event.y))
ball.position += diff
left, top = event.x, event.y
def update():
# Main simulation loop.
graph.after(1000 / FRAMES_PER_SEC, update)
draw()
move()
def draw():
graph.delete(ALL)
# Draw sides.
graph.create_rectangle((0, 0, WALL - BALL_RADIUS, HEIGHT), fill='light green')
graph.create_rectangle((WIDTH - WALL + BALL_RADIUS, 0, WIDTH, HEIGHT), fill='light green')
# Draw floor.
y = HEIGHT - WALL + BALL_RADIUS + 2
graph.create_line((WALL - BALL_RADIUS, y, WIDTH - WALL + BALL_RADIUS, y), fill='blue', width=3)
# Draw all balls.
for ball in balls:
x1 = ball.position.x - BALL_RADIUS
y1 = ball.position.y - BALL_RADIUS
x2 = ball.position.x + BALL_RADIUS
y2 = ball.position.y + BALL_RADIUS
graph.create_oval((x1, y1, x2, y2), fill='red')
graph.update()
def move():
# Move all balls.
for force in simulate_wall, simulate_gravity, simulate_friction:
for ball in balls:
force(ball)
for ball in balls:
ball.update_velocity(balls)
for ball in balls:
ball.move()
def simulate_wall(ball):
# Create viewing boundaries.
if ball.position.x < WALL:
ball.velocity.x += WALL_FORCE
elif ball.position.x > WIDTH - WALL:
ball.velocity.x -= WALL_FORCE
if ball.position.y >= HEIGHT - WALL:
ball.velocity.y *= -1
ball.position.y = HEIGHT - WALL
def simulate_gravity(ball):
# Create a pull.
ball.velocity.y += 50
def simulate_friction(ball):
# Slow velocity down.
ball.velocity *= .9925
def limit_speed(ball):
# Limit ball speed.
if ball.velocity.mag() > SPEED_LIMIT:
ball.velocity /= ball.velocity.mag() / SPEED_LIMIT
def build_balls():
# Create balls variable.
global balls
balls = tuple(Ball(WIDTH, HEIGHT, OFFSET_START, FRAMES_PER_SEC) for ball in xrange(BALLS))
################################################################################
# TWO DIMENTIONAL VECTOR CLASS
class TwoD:
def __init__(self, x, y):
self.x = float(x)
self.y = float(y)
def __repr__(self):
return 'TwoD(%s, %s)' % (self.x, self.y)
def __add__(self, other):
return TwoD(self.x + other.x, self.y + other.y)
def __sub__(self, other):
return TwoD(self.x - other.x, self.y - other.y)
def __mul__(self, other):
return TwoD(self.x * other, self.y * other)
def __div__(self, other):
return TwoD(self.x / other if other else self.x, self.y / other if other else self.y)
def __iadd__(self, other):
self.x += other.x
self.y += other.y
return self
def __isub__(self, other):
self.x -= other.x
self.y -= other.y
return self
def __imul__(self, other):
self.x *= other
self.y *= other
return self
def __idiv__(self, other):
self.x /= other
self.y /= other
return self
def mag(self):
return ((self.x ** 2) + (self.y ** 2)) ** 0.5
################################################################################
# BALL IMPLEMENTATION CLASS
class Ball:
def __init__(self, width, height, offset, move_divider):
self.velocity = TwoD(0, 0)
self.position = TwoD(*(-offset if random.randint(0, 1) else width + offset, random.randint(1, height)))
self.move_divider = move_divider * 5
def update_velocity(self, balls):
vector = TwoD(0, 0)
for ball in balls:
if ball is not self:
if (self.position - ball.position).mag() < (BALL_RADIUS * 2.5):
vector -= (ball.position - self.position)
self.__temp = vector * self.velocity.mag() / vector.mag()
def move(self):
self.velocity += self.__temp
limit_speed(self)
self.position += self.velocity / self.move_divider
################################################################################
# Execute the simulation.
if __name__ == '__main__':
main()
|
Please see http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/502240 for the recipe that inspired the creation of this concept program.