Does Spyder have a good way to detect what is causing a crash?
All the changes we made are in the ArrayPQ class and the node class, the other code works without our changes.
import math
import numpy as np
import numpy.random as rand
import numpy.linalg as linalg
from tkinter import Tk, Frame, Canvas
class ArrayPQ:
def __init__(self, num_balls):
self.collisionNodes = np.array([])
self.pastCollisions = np.zeros(num_balls)
def parent(self, i):
return (i-1) // 2
def right(self, i):
return (i*2) + 1
def left(self, i):
return (i*2) + 2
def insert(self, i, j, value, num_collisions_i, num_collisions_j):
self.pastCollisions[i] = num_collisions_i
self.pastCollisions[j] = num_collisions_j
self.collisionNodes = np.append(self.collisionNodes, node.nodeinit(self, i, j, value, num_collisions_i, num_collisions_j))
self.heapify1(i)
def heapify1(self, i):
l = self.left(i)
r = self.right(i)
end = len(self.collisionNodes)
top = i
if l < end and self.collisionNodes(i) < self.collisionNodes(l):
top = l
if r < end and self.collisionNodes(top) < self.collisionNodes(r):
top = r
if max != i:
self.swap(i, top)
self.heapify1(top)
def heapify2(self, i):
if self.right(i) + len(self.collisionNodes)
def delete(self, i):
self.swap(self, 0, (len(self.collisionNodes) -1))
del self.collisionNodes[len(self.collisionNodes)-1]
self.heapify1(i)
def swap(self, i, j):
self.collisionNodes[i], self.collisionNodes[j] = self.collisionNodes[j],
self.collisionNodes[i]
def get_next(self):
temp = self.collisionsNodes[0]
return temp.BBi, temp.BBj, temp.T, temp.CCi, temp.CCj
class node:
def nodeinit(self, Bi, Bj, T, Ci, Cj):
self.BBi = Bi
self.BBj = Bj
self.TT = T
self.CCi = Ci
self.CCj = Cj
class Painter:
#__init__ performs the construction of a Painter object
def __init__(self, root, scale=500, border=5, refresh_speed=5,
filename='balls.txt', min_radius=5, max_radius=10, num_balls=20):
#width and height are used to set the simulation borders
width = scale + border
height = scale + border
self.time = 0
#setup will set up the necessary graphics window and the ball list
self.setup(root, width, height, border, refresh_speed)
#Check the input parameter 'filename' to load predetermined simulation
#otherwise set up the default simulation
if filename is None:
self.init_balls(max_radius, min_radius, num_balls)
self.num_balls = num_balls
else:
self.num_balls = self.read_balls(scale, filename)
#Create the priority data structure
self.PQ = ArrayPQ(self.num_balls)
#Initialize all possible collision times
self.init_ball_collision_times()
self.init_wall_collision_times()
#draw will draw the graphics to the window
self.draw()
#refresh is a loop method intended to create animations
self.refresh()
#A blank return indicates the end of the function
return
#setup creates the window to display the graphics along with the red border
#of the simulation
def setup(self, root, width, height, border, refresh_speed):
# Draw frame etc
self.app_frame = Frame(root)
self.app_frame.pack()
self.canvas = Canvas(self.app_frame, width = width, height = height)
self.canvas_size = (int(self.canvas.cget('width')),
int(self.canvas.cget('height')))
self.canvas.pack()
self.refresh_speed = refresh_speed
# Work area
self.min_x = border
self.max_x = width - border
self.min_y = border
self.max_y = height - border
#create array to hold the n number of balls
self.balls = []
self.ball_handles = dict()
return
def read_balls(self, scale, filename):
f = open(filename)
num_balls = int(f.readline().strip())
for l in f:
ll = l.strip().split(" ")
x = scale*float(ll[0])
y = scale*float(ll[1])
vx = scale*float(ll[2])
vy = scale*float(ll[3])
radius = scale*float(ll[4])
mass = float(ll[5])
r = int(ll[6])
g = int(ll[7])
b = int(ll[8])
tk_rgb = "#%02x%02x%02x" % (r, g, b)
new_ball = Ball(radius, x, y, vx, vy, mass, tk_rgb)
self.balls.append(new_ball)
return num_balls
def init_balls(self, max_radius, min_radius, num_balls):
for i in np.arange(num_balls):
while(True):
radius = (max_radius - min_radius) * rand.random_sample() +
min_radius
ball_min_x = self.min_x + radius
ball_max_x = self.max_x - radius
x = (ball_max_x - ball_min_x)*rand.random_sample() + ball_min_x
ball_min_y = self.min_y + radius
ball_max_y = self.max_y - radius
y = (ball_max_y - ball_min_y)*rand.random_sample() + ball_min_y
vx = rand.random_sample()
vy = rand.random_sample()
mass = 1.0 # rand.random_sample()
new_ball = Ball(radius, x, y, vx, vy, mass)
if not new_ball.check_overlap(self.balls):
self.balls.append(new_ball)
break
#init_wall_collision_times will set all of the balls' minimum collision time
def init_wall_collision_times(self):
for i in np.arange(len(self.balls)):
bi = self.balls[i]
tix = bi.horizontal_wall_collision_time(self.min_x, self.max_x)
tiy = bi.vertical_wall_collision_time(self.min_y, self.max_y)
self.PQ.insert(i, -1, tix + self.time, self.balls[i].count, -1)
self.PQ.insert(-1, i, tiy + self.time, -1, self.balls[i].count)
return
#init_ball_collision_times will set all of the balls' minimum collision time
#with all other balls and store that time within the ith and jth index of
#PQ.ball_collision_time
def init_ball_collision_times(self):
for i in np.arange(self.num_balls):
bi = self.balls[i]
for j in np.arange(i+1, self.num_balls):
bj = self.balls[j]
tij = bi.ball_collision_time(bj)
self.PQ.insert(i, j, tij + self.time, self.balls[i].count,
self.balls[j].count)
# self.ball_collision_times[i][j] = tij
# self.ball_collision_times[j][i] = tij
return
#walls (horizontal and vertical) and all other balls within the PQ array
def update_collision_times(self, i):
bi = self.balls[i]
tix = bi.horizontal_wall_collision_time(self.min_x, self.max_x)
tiy = bi.vertical_wall_collision_time(self.min_y, self.max_y)
self.PQ.insert(i, -1, tix + self.time,self.balls[i].count, -1)
self.PQ.insert(-1, i, tiy + self.time, -1, self.balls[i].count)
for j in np.arange(self.num_balls):
bj = self.balls[j]
tij = bi.ball_collision_time(bj) + self.time
if i > j:
self.PQ.insert(j, i,
tij,self.balls[j].count,self.balls[i].count)
else:
self.PQ.insert(i, j, tij,self.balls[i].count,
self.balls[j].count)
return
#draw will draw the borders and all balls within self.balls
def draw(self):
#Draw walls
self.canvas.create_line((self.min_x, self.min_y), (self.min_x,
self.max_y), fill = "red")
self.canvas.create_line((self.min_x, self.min_y), (self.max_x,
self.min_y), fill = "red")
self.canvas.create_line((self.min_x, self.max_y), (self.max_x,
self.max_y), fill = "red")
self.canvas.create_line((self.max_x, self.min_y), (self.max_x,
self.max_y), fill = "red")
#Draw balls
for b in self.balls:
obj = self.canvas.create_oval(b.x - b.radius, b.y - b.radius, b.x +
b.radius, b.y + b.radius, outline=b.tk_rgb, fill=b.tk_rgb)
self.ball_handles[b] = obj
self.canvas.update()
#refresh is called to update the state of the simulation
#-each refresh call can be considered one iteration of the simulation
def refresh(self):
#get the next collision
i, j, t, num_collisions_i, num_collision_j = self.PQ.get_next()
#gather the current collisions of the ith and jth ball
current_collisions_i = self.balls[i].count
current_collisions_j = self.balls[j].count
#Check the difference in time between the predicted collision time and
#the current time stamp of the simulation
delta = t - self.time
#If the difference is greater than 1, then just move the balls
if delta > 1.0:
# cap delta to 1.0
for bi in self.balls:
bi.move()
self.canvas.move(self.ball_handles[bi], bi.vx, bi.vy)
self.time += 1.0
#Otherwise a collision has occurred
else:
#Move all balls
for bi in self.balls:
bi.move(delta)
self.canvas.move(self.ball_handles[bi], bi.vx*delta,
bi.vy*delta)
#increment the simulation time stamp
self.time += delta
#Delete the top element within the Priority Queue
self.PQ.delete()
#if i is -1 then this indicates a collision with a vertical wall
#also this if statement checks if the number of collisions recorded
#when the collision returned by PQ.get_next() is equal to the
#number of collisions within the jth ball
#this acts as a test to check if the collision is still valid
if i == -1 and num_collision_j == current_collisions_j:
#compute what happens from the vertical wall collision
self.balls[j].collide_with_vertical_wall()
#update collision times for the jth ball
self.update_collision_times(j)
#if j is -1 then this indicates a collision a horizontal wall
#while also checking if the number of collisions match
#to see if the collision is valid
elif j == -1 and num_collisions_i == current_collisions_i:
#compute what happens from the horizontal wall collision
self.balls[i].collide_with_horizontal_wall()
#update collision times for the ith ball
self.update_collision_times(i)
elif num_collision_j == current_collisions_j and num_collisions_i ==
current_collisions_i:
#Execute collision across the ith and jth ball
self.balls[i].collide_with_ball(self.balls[j])
#update collision times for both the ith and jth ball
self.update_collision_times(i)
self.update_collision_times(j)
#update the canvas to draw the new locations of each ball
self.canvas.update()
self.canvas.after(self.refresh_speed, self.refresh)
def __init__(self, radius, x, y, vx, vy, mass, tk_rgb="#000000"):
self.radius = radius
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.mass = mass
self.tk_rgb = tk_rgb
#since this ball was just initialized, it hasn't had any collisions yet
self.count = 0
return
#move changes the displacement of the ball by the velocity
def move(self, dt=1.0):
self.x += self.vx*dt
self.y += self.vy*dt
return
#check_overlap checks if this ball is overlapping with any other
#ball, it is used to see if a collision has occurred
def check_overlap(self, others):
for b in others:
min_dist = b.radius + self.radius
center_dist = math.sqrt((b.x - self.x)*(b.x - self.x) + \
(b.y - self.y)*(b.y - self.y))
if center_dist < min_dist:
return True
return False
#collide_with_ball computes collision, changing the Ball's velocity
#as well as the other ball's velocity
def collide_with_ball(self, other):
dv_x = other.vx - self.vx
dv_y = other.vy - self.vy
dr_x = other.x - self.x
dr_y = other.y - self.y
sigma = self.radius + other.radius
dv_dr = dv_x * dr_x + dv_y * dr_y
J = 2.0 * self.mass * other.mass * dv_dr/((self.mass +
other.mass)*sigma)
Jx = J * dr_x/sigma
Jy = J * dr_y/sigma
self.vx += Jx/self.mass
self.vy += Jy/self.mass
other.vx -= Jx/other.mass
other.vy -= Jy/other.mass
#Increment the collision count for both balls
self.count += 1
other.count += 1
return
# Compute when an instance of Ball collides with the given Ball other
# Return the timestamp that this will occur
def ball_collision_time(self, other):
dr_x = other.x - self.x
dr_y = other.y - self.y
if dr_x == 0 and dr_y == 0:
return float('inf')
dv_x = other.vx - self.vx
dv_y = other.vy - self.vy
dv_dr = dv_x * dr_x + dv_y * dr_y
if dv_dr > 0:
return float('inf')
dv_dv = dv_x * dv_x + dv_y * dv_y
dr_dr = dr_x * dr_x + dr_y * dr_y
sigma = self.radius + other.radius
d = dv_dr * dv_dr - dv_dv * (dr_dr - sigma * sigma)
# No solution
if d < 0 or dv_dv == 0:
return float('inf')
return - (dv_dr + np.sqrt(d))/dv_dv
#collide_with_horizontal_wall executes the change in the Ball's
#velocity when colliding with a horizontal wall
def collide_with_horizontal_wall(self):
self.vx = -self.vx
self.count += 1
return
#collide_with_vertical_wall executes the change in the Ball's
#velocity when colliding with a vertical wall
def collide_with_vertical_wall(self):
self.vy = -self.vy
self.count += 1
return
# Compute when the instance of Ball collides with a horizontal wall
# Return the time stamp that this will occur
def horizontal_wall_collision_time(self, min_x, max_x):
if self.vx < 0:
# x + delta_t * vx = min_x + radius
return (min_x + self.radius - self.x)/(1.0*self.vx)
if self.vx > 0:
# x + delta_t * vx = max_x - radius
return (max_x - self.radius - self.x)/(1.0*self.vx)
return float('inf')
# Compute when the instance of Ball collides with a vertical wall
# Return the time stamp that this will occur
# Inputs of min_y and max_y
def vertical_wall_collision_time(self, min_y, max_y):
if self.vy < 0:
# y + delta_t * vy = min_y + radius
return (min_y + self.radius - self.y)/(1.0*self.vy)
if self.vy > 0:
# y + delta_t * vy = max_y - radius
return (max_y - self.radius - self.y)/(1.0*self.vy)
return float('inf')
#show_stats will print out the Ball's data
#specifically the radius, position, velocity, mass, and color
def show_stats(self):
print("radius: %f"%self.radius)
print("position: %f, %f"%(self.x, self.y))
print("velocity: %f, %f"%(self.vx, self.vy))
print("mass: %f"%self.mass)
print("rgb: %s"%self.tk_rgb)
return
#Main script
if __name__ == "__main__":
#Set the parameters for the graphics window and simulation
scale = 800
border = 5
#Set radius range for all balls
max_radius = 20
min_radius = 5
#set number of balls
num_balls = 10
#set refresh rate
refresh_speed = 5
rand.seed(12394)
#create the graphics object
root = Tk()
#Create the painter object
p = Painter(root, scale, border, refresh_speed, None, min_radius,max_radius,
num_balls)
#If you have the commented out files in your directory then
#uncomment them to see what they do
#p = Painter(root, scale, border, refresh_speed, 'wallbouncing.txt')
#p = Painter(root, scale, border, refresh_speed, 'p10.txt')
#p = Painter(root, scale, border, refresh_speed, 'billiards4.txt')
#p = Painter(root, scale, border, refresh_speed, 'squeeze.txt')
#run the Painter main loop function (calls refresh many times)
root.mainloop()
Related
I am trying to make realistic water in pygame:
This is till now my code:
from random import randint
import pygame
WIDTH = 700
HEIGHT = 500
win = pygame.display.set_mode((WIDTH, HEIGHT))
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
AQUA = 'aqua'
RADIUS = 1
x, y = 0, HEIGHT//2
K = 1
FORCE = 100
VELOCITY = 0.5
run = True
class Molecule:
def __init__(self, x, y, radius, force, k):
self.x = x
self.y = y
self.radius = radius
self.force = force
self.k = k
self.max_amplitude = y + force/k
self.min_amplitude = y - force/k
self.up = False
self.down = True
self.restore = False
def draw(self, win):
pygame.draw.circle(win, BLACK, (self.x, self.y), self.radius)
def oscillate(self):
if self.y <= self.max_amplitude and self.down == True:
self.y += VELOCITY
if self.y == self.max_amplitude or self.up:
self.up = True
self.down = False
self.y -= VELOCITY
if self.y == self.min_amplitude:
self.up = False
self.down = True
molecules = []
for i in range(100):
FORCE = randint(10, 20)
molecules.append(Molecule(x, y, RADIUS, FORCE, K))
x += 10
while run:
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill(WHITE)
for molecule in molecules:
molecule.draw(win)
molecule.oscillate()
for i in range(len(molecules)):
try:
pygame.draw.line(win, BLACK, (molecules[i].x, molecules[i].y), (molecules[i+1].x, molecules[i+1].y))
pygame.draw.line(win, AQUA, (molecules[i].x, molecules[i].y), (molecules[i+1].x, HEIGHT))
except:
pass
pygame.display.flip()
pygame.quit()
But as may expected the water curve is not smooth:
Look at it:
Sample Img1
I want to connect the two randomly added wave points using a set of circles not line like in this one so that a smooth curve could occur.
And in this way i could add the water color to it such that it will draw aqua lines or my desired color line from the point to the end of screen and all this will end up with smooth water flowing simulation.
Now the question is how could i make the points connect together smoothly into a smooth curve by drawing point circles at relative points?
I suggest sticking the segments with a Bézier curves. Bézier curves can be drawn with pygame.gfxdraw.bezier
Calculate the slopes of the tangents to the points along the wavy waterline:
ts = []
for i in range(len(molecules)):
pa = molecules[max(0, i-1)]
pb = molecules[min(len(molecules)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
Use the the tangents to define 4 control points for each segment and draw the curve with pygame.gfxdraw.bezier:
for i in range(len(molecules)-1):
p0 = molecules[i].x, molecules[i].y
p3 = molecules[i+1].x, molecules[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pygame.gfxdraw.bezier(win, [p0, p1, p2, p3], 4, BLACK)
Complete example:
from random import randint
import pygame
import pygame.gfxdraw
WIDTH = 700
HEIGHT = 500
win = pygame.display.set_mode((WIDTH, HEIGHT))
WHITE = (255, 255, 255)
BLACK = (0, 0, 0)
AQUA = 'aqua'
RADIUS = 1
x, y = 0, HEIGHT//2
K = 1
FORCE = 100
VELOCITY = 0.5
class Molecule:
def __init__(self, x, y, radius, force, k):
self.x = x
self.y = y
self.radius = radius
self.force = force
self.k = k
self.max_amplitude = y + force/k
self.min_amplitude = y - force/k
self.up = False
self.down = True
self.restore = False
def draw(self, win):
pygame.draw.circle(win, BLACK, (self.x, self.y), self.radius)
def oscillate(self):
if self.y <= self.max_amplitude and self.down == True:
self.y += VELOCITY
if self.y == self.max_amplitude or self.up:
self.up = True
self.down = False
self.y -= VELOCITY
if self.y == self.min_amplitude:
self.up = False
self.down = True
molecules = []
for i in range(50):
FORCE = randint(10, 20)
molecules.append(Molecule(x, y, RADIUS, FORCE, K))
x += 20
clock = pygame.time.Clock()
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
win.fill(WHITE)
for molecule in molecules:
molecule.draw(win)
molecule.oscillate()
ts = []
for i in range(len(molecules)):
pa = molecules[max(0, i-1)]
pb = molecules[min(len(molecules)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
for i in range(len(molecules)-1):
p0 = molecules[i].x, molecules[i].y
p3 = molecules[i+1].x, molecules[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pygame.gfxdraw.bezier(win, [p0, p1, p2, p3], 4, BLACK)
for i in range(len(molecules)-1):
pygame.draw.line(win, AQUA, (molecules[i].x, molecules[i].y), (molecules[i].x, HEIGHT))
pygame.display.flip()
pygame.quit()
If you want to "fill" the water, you must calculate the points along the Bézier line and draw a filled polygon. How to calculate a Bézier curve is explained in Trying to make a Bezier Curve on PyGame library How Can I Make a Thicker Bezier in Pygame? and "X". You can use the following function:
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(b, samples):
return [ptOnCurve(b, i/samples) for i in range(samples+1)]
Use the bezier to stitch the wavy water polygon:
ts = []
for i in range(len(molecules)):
pa = molecules[max(0, i-1)]
pb = molecules[min(len(molecules)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
pts = [(WIDTH, HEIGHT), (0, HEIGHT)]
for i in range(len(molecules)-1):
p0 = molecules[i].x, molecules[i].y
p3 = molecules[i+1].x, molecules[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pts += bezier([p0, p1, p2, p3], 4)
Draw the polygon with pygame.draw.polygon():
pygame.draw.polygon(win, AQUA, pts)
Complete example:
from random import randint
import pygame
class Node:
def __init__(self, x, y, force, k, v):
self.x = x
self.y = y
self.y0 = y
self.force = force
self.k = k
self.v = v
self.direction = 1
def oscillate(self):
self.y += self.v * self.direction
if self.y0 - self.force / self.k > self.y or self.y0 + self.force / self.k < self.y:
self.direction *= -1
def draw(self, surf):
pygame.draw.circle(surf, "black", (self.x, self.y), 3)
window = pygame.display.set_mode((700, 500))
clock = pygame.time.Clock()
width, height = window.get_size()
no_of_nodes = 25
dx = width / no_of_nodes
nodes = [Node(i*dx, height//2, randint(15, 30), 1, 0.5) for i in range(no_of_nodes+1)]
def ptOnCurve(b, t):
q = b.copy()
for k in range(1, len(b)):
for i in range(len(b) - k):
q[i] = (1-t) * q[i][0] + t * q[i+1][0], (1-t) * q[i][1] + t * q[i+1][1]
return round(q[0][0]), round(q[0][1])
def bezier(b, samples):
return [ptOnCurve(b, i/samples) for i in range(samples+1)]
run = True
while run:
clock.tick(100)
for event in pygame.event.get():
if event.type == pygame.QUIT:
run = False
for molecule in nodes:
molecule.oscillate()
ts = []
for i in range(len(nodes)):
pa = nodes[max(0, i-1)]
pb = nodes[min(len(nodes)-1, i+1)]
ts.append((pb.y-pa.y) / (pb.x-pa.x))
pts = [(width, height), (0, height)]
for i in range(len(nodes)-1):
p0 = nodes[i].x, nodes[i].y
p3 = nodes[i+1].x, nodes[i+1].y
p1 = p0[0] + 10, p0[1] + 10 * ts[i]
p2 = p3[0] - 10, p3[1] - 10 * ts[i+1]
pts += bezier([p0, p1, p2, p3], 4)
window.fill("white")
pygame.draw.polygon(window, 'aqua', pts)
for molecule in nodes:
molecule.draw(window)
pygame.display.flip()
pygame.quit()
exit()
I'm trying to port to Python the "Controlled Circle Packing with Processing" algorithm that I found here:
http://www.codeplastic.com/2017/09/09/controlled-circle-packing-with-processing/?replytocom=22#respond
For now my goal is just to make it work, before I tweak it for my own needs. This question is not about the best way to do circle packing.
So far here is what I have:
#!/usr/bin/python
# coding: utf-8
import numpy as np
import matplotlib.pyplot as plt
from random import uniform
class Ball:
def __init__(self, x, y, radius):
self.r = radius
self.acceleration = np.array([0, 0])
self.velocity = np.array([uniform(0, 1),
uniform(0, 1)])
self.position = np.array([x, y])
#property
def x(self):
return self.position[0]
#property
def y(self):
return self.position[1]
def applyForce(self, force):
self.acceleration = np.add(self.acceleration, force)
def update(self):
self.velocity = np.add(self.velocity, self.acceleration)
self.position = np.add(self.position, self.velocity)
self.acceleration *= 0
class Pack:
def __init__(self, radius, list_balls):
self.list_balls = list_balls
self.r = radius
self.list_separate_forces = [np.array([0, 0])] * len(self.list_balls)
self.list_near_balls = [0] * len(self.list_balls)
def _normalize(self, v):
norm = np.linalg.norm(v)
if norm == 0:
return v
return v / norm
def run(self):
for i in range(300):
print(i)
for ball in self.list_balls:
self.checkBorders(ball)
self.checkBallPositions(ball)
self.applySeparationForcesToBall(ball)
def checkBorders(self, ball):
if (ball.x - ball.r) < - self.r or (ball.x + ball.r) > self.r:
ball.velocity[0] *= -1
ball.update()
if (ball.y - ball.r) < -self.r or (ball.y + ball.r) > self.r:
ball.velocity[1] *= -1
ball.update()
def checkBallPositions(self, ball):
list_neighbours = [e for e in self.list_balls if e is not ball]
for neighbour in list_neighbours:
d = self._distanceBalls(ball, neighbour)
if d < (ball.r + neighbour.r):
return
ball.velocity[0] = 0
ball.velocity[1] = 0
def getSeparationForce(self, c1, c2):
steer = np.array([0, 0])
d = self._distanceBalls(c1, c2)
if d > 0 and d < (c1.r + c2.r):
diff = np.subtract(c1.position, c2.position)
diff = self._normalize(diff)
diff = np.divide(diff, d)
steer = np.add(steer, diff)
return steer
def _distanceBalls(self, c1, c2):
x1, y1 = c1.x, c1.y
x2, y2 = c2.x, c2.y
dist = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
return dist
def applySeparationForcesToBall(self, ball):
i = self.list_balls.index(ball)
list_neighbours = [e for e in self.list_balls if e is not ball]
for neighbour in list_neighbours:
j = self.list_balls.index(neighbour)
forceij = self.getSeparationForce(ball, neighbour)
if np.linalg.norm(forceij) > 0:
self.list_separate_forces[i] = np.add(self.list_separate_forces[i], forceij)
self.list_separate_forces[j] = np.subtract(self.list_separate_forces[j], forceij)
self.list_near_balls[i] += 1
self.list_near_balls[j] += 1
if self.list_near_balls[i] > 0:
self.list_separate_forces[i] = np.divide(self.list_separate_forces[i], self.list_near_balls[i])
if np.linalg.norm(self.list_separate_forces[i]) > 0:
self.list_separate_forces[i] = self._normalize(self.list_separate_forces[i])
self.list_separate_forces[i] = np.subtract(self.list_separate_forces[i], ball.velocity)
self.list_separate_forces[i] = np.clip(self.list_separate_forces[i], a_min=0, a_max=np.array([1]))
separation = self.list_separate_forces[i]
ball.applyForce(separation)
ball.update()
list_balls = list()
for i in range(10):
b = Ball(0, 0, 7)
list_balls.append(b)
p = Pack(30, list_balls)
p.run()
plt.axes()
# Big container
circle = plt.Circle((0, 0), radius=30, fc='none', ec='k')
plt.gca().add_patch(circle)
for c in list_balls:
ball = plt.Circle((c.x, c.y), radius=c.r, picker=True, fc='none', ec='k')
plt.gca().add_patch(ball)
plt.axis('scaled')
plt.show()
The code was originally written with Processing, I did my best to use numpy instead.
I'm not quite sure of my checkBallPosition, the original author uses a count variable that looks useless to me. I also wonder why the steer vector in the original code has a dimension of 3.
So far, here is what my code yields:
The circles (I had to rename them balls to not conflict with Circle from matplotlib) overlap and don't seem to get away from each other. I don't think I'm really far but I would need a bit of help to find what's wrong with my code. Could you help me please ?
EDIT: I realize that I probably need to do several passes. Maybe the Processing package (language ?) runs the run function several times. It actually makes sense to me, this problem is very similar to molecular mechanics optimization and it's an iterative process.
My question can now be a bit more specific: it seems the checkBorders function doesn't do its job properly and doesn't rebound the circles properly. But given its simplicity, I'd say the bug is in applySeparationForcesToBall, I probably don't apply the forces correctly.
Ok after days of fiddling, I managed to do it:
Here is the complete code:
#!/usr/bin/python
# coding: utf-8
"""
http://www.codeplastic.com/2017/09/09/controlled-circle-packing-with-processing/
https://stackoverflow.com/questions/573084/how-to-calculate-bounce-angle/573206#573206
https://stackoverflow.com/questions/4613345/python-pygame-ball-collision-with-interior-of-circle
"""
import numpy as np
import matplotlib.pyplot as plt
from random import randint
from random import uniform
from matplotlib import animation
class Ball:
def __init__(self, x, y, radius):
self.r = radius
self.acceleration = np.array([0, 0])
self.velocity = np.array([uniform(0, 1),
uniform(0, 1)])
self.position = np.array([x, y])
#property
def x(self):
return self.position[0]
#property
def y(self):
return self.position[1]
def applyForce(self, force):
self.acceleration = np.add(self.acceleration, force)
def _normalize(self, v):
norm = np.linalg.norm(v)
if norm == 0:
return v
return v / norm
def update(self):
self.velocity = np.add(self.velocity, self.acceleration)
self.position = np.add(self.position, self.velocity)
self.acceleration *= 0
class Pack:
def __init__(self, radius, list_balls):
self.iter = 0
self.list_balls = list_balls
self.r = radius
self.list_separate_forces = [np.array([0, 0])] * len(self.list_balls)
self.list_near_balls = [0] * len(self.list_balls)
self.wait = True
def _normalize(self, v):
norm = np.linalg.norm(v)
if norm == 0:
return v
return v / norm
def run(self):
self.iter += 1
for ball in self.list_balls:
self.checkBorders(ball)
self.checkBallPositions(ball)
self.applySeparationForcesToBall(ball)
print(ball.position)
print("\n")
def checkBorders(self, ball):
d = np.sqrt(ball.x**2 + ball.y**2)
if d >= self.r - ball.r:
vr = self._normalize(ball.velocity) * ball.r
# P1 is collision point between circle and container
P1x = ball.x + vr[0]
P1y = ball.y + vr[1]
P1 = np.array([P1x, P1y])
# Normal vector
n_v = -1 * self._normalize(P1)
u = np.dot(ball.velocity, n_v) * n_v
w = np.subtract(ball.velocity, u)
ball.velocity = np.subtract(w, u)
ball.update()
def checkBallPositions(self, ball):
i = self.list_balls.index(ball)
# for neighbour in list_neighbours:
# ot a full loop; if we had two full loops, we'd compare every
# particle to every other particle twice over (and compare each
# particle to itself)
for neighbour in self.list_balls[i + 1:]:
d = self._distanceBalls(ball, neighbour)
if d < (ball.r + neighbour.r):
return
ball.velocity[0] = 0
ball.velocity[1] = 0
def getSeparationForce(self, c1, c2):
steer = np.array([0, 0])
d = self._distanceBalls(c1, c2)
if d > 0 and d < (c1.r + c2.r):
diff = np.subtract(c1.position, c2.position)
diff = self._normalize(diff)
diff = np.divide(diff, 1 / d**2)
steer = np.add(steer, diff)
return steer
def _distanceBalls(self, c1, c2):
x1, y1 = c1.x, c1.y
x2, y2 = c2.x, c2.y
dist = np.sqrt((x2 - x1)**2 + (y2 - y1)**2)
return dist
def applySeparationForcesToBall(self, ball):
i = self.list_balls.index(ball)
for neighbour in self.list_balls[i + 1:]:
j = self.list_balls.index(neighbour)
forceij = self.getSeparationForce(ball, neighbour)
if np.linalg.norm(forceij) > 0:
self.list_separate_forces[i] = np.add(self.list_separate_forces[i], forceij)
self.list_separate_forces[j] = np.subtract(self.list_separate_forces[j], forceij)
self.list_near_balls[i] += 1
self.list_near_balls[j] += 1
if np.linalg.norm(self.list_separate_forces[i]) > 0:
self.list_separate_forces[i] = np.subtract(self.list_separate_forces[i], ball.velocity)
if self.list_near_balls[i] > 0:
self.list_separate_forces[i] = np.divide(self.list_separate_forces[i], self.list_near_balls[i])
separation = self.list_separate_forces[i]
ball.applyForce(separation)
ball.update()
list_balls = list()
for i in range(25):
# b = Ball(randint(-15, 15), randint(-15, 15), 5)
b = Ball(0, 0, 5)
list_balls.append(b)
p = Pack(30, list_balls)
fig = plt.figure()
circle = plt.Circle((0, 0), radius=30, fc='none', ec='k')
plt.gca().add_patch(circle)
plt.axis('scaled')
plt.axes().set_xlim(-50, 50)
plt.axes().set_ylim(-50, 50)
def draw(i):
patches = []
p.run()
fig.clf()
circle = plt.Circle((0, 0), radius=30, fc='none', ec='k')
plt.gca().add_patch(circle)
plt.axis('scaled')
plt.axes().set_xlim(-50, 50)
plt.axes().set_ylim(-50, 50)
for c in list_balls:
ball = plt.Circle((c.x, c.y), radius=c.r, picker=True, fc='none', ec='k')
patches.append(plt.gca().add_patch(ball))
return patches
co = False
anim = animation.FuncAnimation(fig, draw,
frames=500, interval=2, blit=True)
# plt.show()
anim.save('line2.gif', dpi=80, writer='imagemagick')
From the original code, I modified the checkBorder function to bounce the circles properly from the edge, and changed the separation force between circles, it was too low. I know my question was a bit too vague from start, but I would have appreciated a more constructive feedback.
So I'm new to GUI and Tkinter. I'm working on a one paddle pong game using Tkinter and I need the paddle to act like a paddle and bounce the disk off when the disk hits the paddle from the right or the left. The current code I have does bounce the disk of the paddles location on the x axis but it doesn't let the disk get past the line_x. Am I thinking in the right direction? or am I way off? It would be awesome if someone can fix my code so it works. This is probably very easy for someone that's been working with GUI's a while but I'm stomped. Please help.
from tkinter import *
import random
class ControlAnimation:
def __init__(self):
my_window = Tk() # create a window
my_window.title("Control Animation Demo")
self.width = 400
self.height = 200
self.line_x = 350
self.line_top = 75
self.line_bot = 125
self.paddle_width = 10
self.dy = 5
self.sleep_time = 50
self.is_stopped = False
self.my_canvas = Canvas(my_window, bg = 'white', \
width = self.width, height = self.height)
self.my_canvas.pack()
frm_control = Frame(my_window) # for comand buttons below canvas
frm_control.pack()
btn_stop = Button(frm_control, text = 'Stop', \
command = self.stop)
btn_stop.pack(side = LEFT)
btn_resume = Button(frm_control, text = 'Resume', \
command = self.resume)
btn_resume.pack(side = LEFT)
btn_faster = Button(frm_control, text = 'Faster', \
command = self.faster)
btn_faster.pack(side = LEFT)
btn_slower = Button(frm_control, text = 'Slower', \
command = self.slower)
btn_slower.pack(side = LEFT)
self.radius = 20
self.x = self.radius # just to start; y is at canvas center
self.y = self.height/2
# (x, y) is center of disk for this program, but ...
# recall: x1,y1 and x2,y2 form a bounding box for disk
self.my_canvas.create_oval(\
self.x - self.radius, self.height/2 + self.radius,\
self.x + self.radius, self.height/2 - self.radius,\
fill = "red", tags = "disk")
self.my_canvas.create_line(self.line_x, self.line_top, \
self.line_x, self.line_bot, \
width = self.paddle_width, fill = "blue", tags = "paddle")
self.my_canvas.bind("<KeyPress-Up>", self.move_paddle)
self.my_canvas.bind("<KeyPress-Down>", self.move_paddle)
self.my_canvas.bind("<B1-Motion>", self.left_click_paddle)
self.my_canvas.bind("<B3-Motion>", self.right_click_paddle)
self.animate()
self.my_canvas.focus_set()
my_window.mainloop()
def stop(self):
self.is_stopped = True
def resume(self):
self.is_stopped = False
self.animate()
def faster(self):
if self.sleep_time > 5:
self.sleep_time -= 15
def slower(self):
self.sleep_time += 15
def animate(self):
dx = 3
dy = 2
while not self.is_stopped :
self.my_canvas.move("disk", dx, dy) # move right
self.my_canvas.after(self.sleep_time) # sleep for a few ms
# redraw/update the canvas w/ new oval position
self.my_canvas.update()
# increment x to set up for next re-draw
r = random.randint(-1, 1)
self.x += dx # moves the disk
if self.x + self.radius > self.width: # hit right boundary
dx = -dx + r # add randomness
elif self.x - self.radius <= 0: # hit left boundary
dx = -dx + r # add randomness
elif self.x + self.radius > self.line_x and self.x + self.radius <= self.line_top:
dx = -dx + r
#elif self.x - self.radius <= self.line_x:
#dx = -dx + r
# increment y to set up for next re-draw
self.y += dy
if self.y + self.radius > self.height: # hit bottom boundary
dy = -dy
elif self.y - self.radius <= 0: # hit top boundary
dy = -dy
def left_click_paddle(self, event):
print(" clicked at =", event.x, event.y)
print("-"*30)
self.move_paddle( -self.dy)
def right_click_paddle(self, event):
print(" clicked at =", event.x, event.y)
print("-"*30)
self.move_paddle( self.dy)
def move_paddle(self, increment):
self.line_top += increment
self.line_bot += increment
self.my_canvas.delete("paddle")
self.my_canvas.create_line(self.line_x, self.line_top, \
self.line_x, self.line_bot, \
width = 10, fill = "blue", tags = "paddle")
ControlAnimation() # create instance of the GUI
In your movement loop, the logic to check whether to trigger a direction change in x is not complete. It should be something like this:
# new disk x, y positions
self.x += dx
self.y += dy
# Change "dx" sign if the ball hit something horizontally
if self.x + self.radius > self.width-1:
# ball hit right frame boundary
dx = -dx + r
elif self.x - self.radius <= 1:
# ball hit left frame boundary
dx = -dx + r
elif ( self.line_x <= self.x+self.radius <= self.line_x + 2*dx
and self.line_top <= self.y <= self.line_bot ):
# ball hit paddle from the left
dx = -dx + r
elif ( self.line_x + 2*dx <= self.x-self.radius <= self.line_x
and self.line_top <= self.y <= self.line_bot ):
# ball hit paddle from the right
dx = -dx + r
I just checked here to make sure this question was allowed, and it seems that it is so here I go:
I am currently making a 2D physics engine as a small project. I have a class called circle which has properties such as radius, rotation, position, and velocity:
class circle():
def __init__(self, radius = 10, r = 0.0, x = 0, y = 0, Vr = 0, Vx = 0, Vy = 0):
self.radius = radius
self.r = r
self.x = x
self.y = y
self.Vr = Vr
self.Vx = Vx
self.Vy = Vy
The class has a method called CheckCollisions(), which checks if the distance between its centre and another circle's centre is less than the sum of their radii:
def CheckCollisions(self):
for c in circles:
distance = math.sqrt((c.x - self.x)*(c.x - self.x) + (c.y - self.y)*(c.y - self.y))
if distance < self.radius + c.radius:
print('!')
else:
print('')
The idea is that on detecting the collision, forces can be applied as vectors to each object as a response to the impact.
When my code runs, I see constant exclamation marks appearing in the shell, despite the circles not colliding. What is causing this? Perhaps something in my calculation of distance is incorrect?
Full code:
import pygame, random, math
from pygame.locals import*
# set up pygame window
(width, height) = (1000, 800)
screen = pygame.display.set_mode((width,height))
pygame.display.set_caption('Impulse Physics v0.1 BETA')
pen = pygame.image.load('Pen.png').convert()
background = (0, 0, 0)
class circle():
def __init__(self, radius = 10, r = 0.0, x = 0, y = 0, Vr = 0, Vx = 0, Vy = 0):
self.radius = radius
# position and rotation
self.r = r
self.x = x
self.y = y
# velocity
self.Vr = Vr
self.Vx = Vx
self.Vy = Vy
def CheckCollisions(self):
for c in circles:
# use pythagoras to find direct distance between centres
distance = math.sqrt((c.x - self.x)*(c.x - self.x) + (c.y - self.y)*(c.y - self.y))
if distance < self.radius + c.radius:
print('!')
else:
print('')
def Move(self):
# apply slight "air resistance"
self.Vx = self.Vx * 0.9999
# gravity. REMEMBER y axis is inverted in pygame!
self.Vy = self.Vy + 0.15
# move object
self.x = self.x + self.Vx
self.y = self.y + self.Vy
self.r = self.r + self.Vr
self.CheckCollisions()
# check if colliding with the sides of the window
if self.y + self.radius > height:
self.Vy = self.Vy * -0.98
self.y = self.y + self.Vy
if (self.x + self.radius > width) or (self.x - self.radius < 0):
self.Vx = self.Vx * -0.98
self.x = self.x + self.Vx
def Render(self):
penX = self.x
penY = self.y
penR = self.r
screen.blit(pen, (penX, penY))
# draw the radius of the circle
for counter in range(self.radius):
penX = self.x + (math.sin(penR) * counter)
penY = self.y - (math.cos(penR) * counter)
screen.blit(pen, (penX, penY))
# draw the circumference of the circle
for counter in range(self.radius * 20):
penR = counter * (360 / self.radius * 20)
penX = self.x + (math.sin(penR) * self.radius)
penY = self.y + (math.cos(penR) * self.radius)
screen.blit(pen, (penX, penY))
circles = []
#create objects here
c1 = circle(100, 0, 400, 400, 0.1, 4)
circles.append(c1)
c2 = circle(50, 0, 50, 50, 0.08, 10)
circles.append(c2)
c3 = circle(10, 0, 300, 200, 0.02, -3)
circles.append(c3)
running = True
while running:
screen.fill(background)
for obj in circles:
obj.Move()
obj.Render()
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
pygame.display.flip()
pygame.quit()
In short: a circle collides with itself. The reason is simply that the circles list contains [c1,c2,c3] and thus checks are done against the circles themselves.
Now for c1 you check whether there is a collision so it iterates over the circles and the first thing it checks is whether it collides with itself (since c1 is the first element in the list). And obviously it does (your test looks if the distance is less than the sum of the circles radiuses, but the distance is zero). If none of the circles collide, there will thus be three exclamation marks (one for each circle).
You can resolve this error by performing a reference equality check first:
def CheckCollisions(self):
for c in circles:
if c is not self:
distance = math.sqrt((c.x - self.x)*(c.x - self.x) + (c.y - self.y)*(c.y - self.y))
#...
import pygame
import random
import numpy as np
import matplotlib.pyplot as plt
import math
number_of_particles = 70
my_particles = []
background_colour = (255,255,255)
width, height = 500, 500
sigma = 1
e = 1
dt = 0.1
v = 0
a = 0
r = 1
def r(p1,p2):
dx = p1.x - p2.x
dy = p1.y - p2.y
angle = 0.5 * math.pi - math.atan2(dy, dx)
dist = np.hypot(dx, dy)
return dist
def collide(p1, p2):
dx = p1.x - p2.x
dy = p1.y - p2.y
dist = np.hypot(dx, dy)
if dist < (p1.size + p2.size):
tangent = math.atan2(dy, dx)
angle = 0.5 * np.pi + tangent
angle1 = 2*tangent - p1.angle
angle2 = 2*tangent - p2.angle
speed1 = p2.speed
speed2 = p1.speed
(p1.angle, p1.speed) = (angle1, speed1)
(p2.angle, p2.speed) = (angle2, speed2)
overlap = 0.5*(p1.size + p2.size - dist+1)
p1.x += np.sin(angle) * overlap
p1.y -= np.cos(angle) * overlap
p2.x -= np.sin(angle) * overlap
p2.y += np.cos(angle) * overlap
def LJ(r):
return -24*e*((2/r*(sigma/r)**12)-1/r*(sigma/r)**6)
def verlet():
a1 = -LJ(r(p1,p2))
r = r + dt*v+0.5*dt**2*a1
a2 = -LJ(r(p1,p2))
v = v + 0.5*dt*(a1+a2)
return r, v
class Particle():
def __init__(self, (x, y), size):
self.x = x
self.y = y
self.size = size
self.colour = (0, 0, 255)
self.thickness = 1
self.speed = 0
self.angle = 0
def display(self):
pygame.draw.circle(screen, self.colour, (int(self.x), int(self.y)), self.size, self.thickness)
def move(self):
self.x += np.sin(self.angle)
self.y -= np.cos(self.angle)
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
self.angle = - self.angle
elif self.x < self.size:
self.x = 2*self.size - self.x
self.angle = - self.angle
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
self.angle = np.pi - self.angle
elif self.y < self.size:
self.y = 2*self.size - self.y
self.angle = np.pi - self.angle
screen = pygame.display.set_mode((width, height))
for n in range(number_of_particles):
x = random.randint(15, width-15)
y = random.randint(15, height-15)
particle = Particle((x, y), 15)
particle.speed = random.random()
particle.angle = random.uniform(0, np.pi*2)
my_particles.append(particle)
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill(background_colour)
for i, particle in enumerate(my_particles):
particle.move()
particle.bounce()
for particle2 in my_particles[i+1:]:
collide(particle, particle2)
particle.display()
pygame.display.flip()
pygame.quit()
I wanted to simulate particles by Lennard-Jones potential. My problem with this code is that I do not know how to use the Verlet algorithm.
I do not know where I should implement the Verlet algorithm; inside the class or outside?
How can I use velocity from the Verlet algorithm in the move method?
Is my implementation of the Verlet algorithm correct, or should I use arrays for saving results?
What else should I change to make it work?
You can keep the dynamical variables, position and velocity, inside the class instances, however then each class needs an acceleration vector to accumulate the force contributions. The Verlet integrator has the role of a controller, it acts from outside on the collection of all particles. Keep the angle out of the computations, the forth and back with trigonometric functions and their inverses is not necessary. Make position, velocity and acceleration all 2D vectors.
One way to implement the velocity Verlet variant is (see https://stackoverflow.com/tags/verlet-integration/info)
verlet_step:
v += a*0.5*dt;
x += v*dt; t += dt;
do_collisions(t,x,v,dt);
a = eval_a(x);
v += a*0.5*dt;
do_statistics(t,x,v);
which supposes a vectorized variant. In your framework, there would be some iterations over the particle collection to include,
verlet_step:
for p in particles:
p.v += p.a*0.5*dt; p.x += p.v*dt;
t += dt;
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
collide(p1,p2);
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
apply_LJ_forces(p1,p2);
for p in particles:
p.v += p.a*0.5*dt;
do_statistics(t,x,v);
No, you could not have done nothing wrong since you did not actually call the Verlet function to update position and velocity. And no, a strict vectorization is not necessary, see above. The implicit vectorization via the particles array is sufficient. You would only need a full vectorization if you wanted to compare with the results of a standard integrator like those in scipy.integrate using the same model to provide the ODE function.
Code with some add-ons but without collisions, desingularized potential
import pygame
import random
import numpy as np
import matplotlib.pyplot as plt
import math
background_colour = (255,255,255)
width, height = 500, 500
aafac = 2 # anti-aliasing factor screen to off-screen image
number_of_particles = 50
my_particles = []
sigma = 10
sigma2 = sigma*sigma
e = 5
dt = 0.1 # simulation time interval between frames
timesteps = 10 # intermediate invisible steps of length dt/timesteps
def LJ_force(p1,p2):
rx = p1.x - p2.x
ry = p1.y - p2.y
r2 = rx*rx+ry*ry
r2s = r2/sigma2+1
r6s = r2s*r2s*r2s
f = 24*e*( 2/(r6s*r6s) - 1/(r6s) )
p1.ax += f*(rx/r2)
p1.ay += f*(ry/r2)
p2.ax -= f*(rx/r2)
p2.ay -= f*(ry/r2)
def Verlet_step(particles, h):
for p in particles:
p.verlet1_update_vx(h);
p.bounce()
#t += h;
for i, p1 in enumerate(particles):
for p2 in particles[i+1:]:
LJ_force(p1,p2);
for p in particles:
p.verlet2_update_v(h);
class Particle():
def __init__(self, (x, y), (vx,vy), size):
self.x = x
self.y = y
self.vx = vx
self.vy = vy
self.size = size
self.colour = (0, 0, 255)
self.thickness = 2
self.ax = 0
self.ay = 0
def verlet1_update_vx(self,h):
self.vx += self.ax*h/2
self.vy += self.ay*h/2
self.x += self.vx*h
self.y += self.vy*h
self.ax = 0
self.ay = 0
def verlet2_update_v(self,h):
self.vx += self.ax*h/2
self.vy += self.ay*h/2
def display(self,screen, aa):
pygame.draw.circle(screen, self.colour, (int(aa*self.x+0.5), int(aa*self.y+0.5)), aa*self.size, aa*self.thickness)
def bounce(self):
if self.x > width - self.size:
self.x = 2*(width - self.size) - self.x
self.vx = - self.vx
elif self.x < self.size:
self.x = 2*self.size - self.x
self.vx = - self.vx
if self.y > height - self.size:
self.y = 2*(height - self.size) - self.y
self.vy = - self.vy
elif self.y < self.size:
self.y = 2*self.size - self.y
self.vy = - self.vy
#------------ end class particle ------------
#------------ start main program ------------
for n in range(number_of_particles):
x = 1.0*random.randint(15, width-15)
y = 1.0*random.randint(15, height-15)
vx, vy = 0., 0.
for k in range(6):
vx += random.randint(-10, 10)/2.
vy += random.randint(-10, 10)/2.
particle = Particle((x, y),(vx,vy), 10)
my_particles.append(particle)
#--------- pygame event loop ----------
screen = pygame.display.set_mode((width, height))
offscreen = pygame.Surface((aafac*width, aafac*height))
running = True
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
offscreen.fill(background_colour)
for k in range(timesteps):
Verlet_step(my_particles, dt/timesteps)
for particle in my_particles:
particle.display(offscreen, aafac)
pygame.transform.smoothscale(offscreen, (width,height), screen)
pygame.display.flip()
pygame.quit()