]When running the simulation the particles can overlap and freak out a bit. I think because it's a discrete simulation when the collision is detected the particles have already overlapped so the sections which calculates the final velocity doesn't work right.
I solved the same problem for the particles and the screen boundary but I can't figure out how do to it for a particle-particle collision. Any help would be greatly appreciated
import pygame
import random
import math
import numpy as np
width, height = 700, 450
screen = pygame.display.set_mode((width, height))
particles = []
no_particles = 10
tick_speed = 200
class Particle:
def __init__(self, x, y, r):
self.r = r
self.pos = np.array([x, y])
self.vel = np.array([random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5)])
self.acc = np.array([0, 0]) #tick_speed/(tick_speed * 10)
def display(self):
pygame.draw.circle(screen, (227, 53, 15), (int(self.pos[0]), int(self.pos[1])), self.r, 1)
def move(self):
self.vel = self.vel + self.acc
self.pos = self.pos + self.vel
def bounce(self):
if self.pos[1] > height - self.r:
self.pos[1] = 2*(height - self.r) - self.pos[1]
self.vel[1] = -self.vel[1]
elif self.pos[1] < self.r:
self.pos[1] = 2*self.r - self.pos[1]
self.vel[1] = -self.vel[1]
if self.pos[0] + self.r > width:
self.pos[0] = 2*(width - self.r) - self.pos[0]
self.vel[0] = -self.vel[0]
elif self.pos[0] < self.r:
self.pos[0] = 2*self.r - self.pos[0]
self.vel[0] = -self.vel[0]
#classmethod
def collision(cls, p1, p2):
dc = math.hypot((p1.pos[0]-p2.pos[0]), (p1.pos[1]-p2.pos[1]))
if dc <= p1.r + p2.r:
x1, y1 = p1.pos[0], p1.pos[1]
x2, y2 = p2.pos[0], p2.pos[1]
m1, m2 = p1.r**2, p2.r**2
n = np.array([x2-x1, y2-y1])
un = n / np.linalg.norm(n)
ut = np.array([-un[1], un[0]])
v1 = p1.vel
v2 = p2.vel
v1n = np.dot(un, v1)
v1t = np.dot(ut, v1)
v2n = np.dot(un, v2)
v2t = np.dot(ut, v2)
v1n_prime_s = (v1n * (m1 - m2) + 2*m2*v2n) / (m1 + m2)
v2n_prime_s = (v2n * (m2 - m1) + 2*m1*v1n) / (m1 + m2)
v1n_prime = v1n_prime_s * un
v1t_prime = v1t * ut
v2n_prime = v2n_prime_s * un
v2t_prime = v2t * ut
u1 = v1n_prime + v1t_prime
u2 = v2n_prime + v2t_prime
p1.vel = u1
p2.vel = u2
while len(particles) < no_particles:
r = random.randint(10, 20)
x = random.randint(r, width-r)
y = random.randint(r, height-r)
collide = False
for particle in particles:
d = (particle.pos[0] - x)**2 + (particle.pos[1] - y)**2
if d < (r + particle.r)**2:
collide = True
break
if not collide:
particles.append(Particle(x, y, random.randint(10, 20)))
running = True
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((255, 255, 255))
for particle in particles:
particle.move()
particle.bounce()
for particle2 in [p for p in particles if p!= particle]:
particle.collision(particle, particle2)
particle.display()
pygame.display.flip()
clock.tick(tick_speed)
pygame.quit()
quit()
The problem is that you are checking and resolving collision multiple times for the same balls in a single iteration.
Since the direction has already once been "reflected" once, these dot products,
v1n = np.dot(un, v1)
v1t = np.dot(ut, v1)
v2n = np.dot(un, v2)
v2t = np.dot(ut, v2)
yield the opposite direction in the next collision resolution, which causes the balls to move towards each other.
"Energy" is being added to the system every time the balls collide, because of these,
p1.r**2, p2.r**2
2*m2*v2n, 2*m1*v1n
which causes the freak out that you mention.
Solution is to simply check and resolve the collisions once:
for i in range(len(particles)):
particles[i].move()
particles[i].bounce()
for j in range(i + 1, len(particles)):
particles[i].collision(particles[i], particles[j])
particles[i].display()
As Rabbid76 pointed out, this fails as the number of particles increases. If you statically resolve the collision first, everything will work since the collision will have already been resolved and your dynamic resolution code will set the velocities accordingly.
#classmethod
def resolveStatically(cls, n, dc, p1, p2):
displacement = (dc - p1.r - p2.r) * 0.5
p1.pos[0] += displacement * (n[0] / dc)
p1.pos[1] += displacement * (n[1] / dc)
p2.pos[0] -= displacement * (n[0] / dc)
p2.pos[1] -= displacement * (n[1] / dc)
#classmethod
def collision(cls, p1, p2):
dc = math.hypot((p1.pos[0]-p2.pos[0]), (p1.pos[1]-p2.pos[1]))
if dc <= p1.r + p2.r:
#...
n = np.array([x2-x1, y2-y1])
cls.resolveStatically(n, dc, p1, p2)
Full code:
import pygame
import random
import math
import numpy as np
width, height = 700, 450
screen = pygame.display.set_mode((width, height))
particles = []
no_particles = 100
tick_speed = 200
class Particle:
def __init__(self, x, y, r):
self.r = r
self.pos = np.array([x, y])
self.vel = np.array([random.uniform(-0.5, 0.5), random.uniform(-0.5, 0.5)])
self.acc = np.array([0, 0]) #tick_speed/(tick_speed * 10)
def display(self):
pygame.draw.circle(screen, (227, 53, 15), (int(self.pos[0]), int(self.pos[1])), self.r, 1)
def move(self):
self.vel = self.vel + self.acc
self.pos = self.pos + self.vel
def bounce(self):
if self.pos[1] > height - self.r:
self.pos[1] = 2*(height - self.r) - self.pos[1]
self.vel[1] = -self.vel[1]
elif self.pos[1] < self.r:
self.pos[1] = 2*self.r - self.pos[1]
self.vel[1] = -self.vel[1]
if self.pos[0] + self.r > width:
self.pos[0] = 2*(width - self.r) - self.pos[0]
self.vel[0] = -self.vel[0]
elif self.pos[0] < self.r:
self.pos[0] = 2*self.r - self.pos[0]
self.vel[0] = -self.vel[0]
#classmethod
def resolveStatically(cls, n, dc, p1, p2):
displacement = (dc - p1.r - p2.r) * 0.5
p1.pos[0] += displacement * (n[0] / dc)
p1.pos[1] += displacement * (n[1] / dc)
p2.pos[0] -= displacement * (n[0] / dc)
p2.pos[1] -= displacement * (n[1] / dc)
#classmethod
def collision(cls, p1, p2):
dc = math.hypot((p1.pos[0]-p2.pos[0]), (p1.pos[1]-p2.pos[1]))
if dc <= p1.r + p2.r:
x1, y1 = p1.pos[0], p1.pos[1]
x2, y2 = p2.pos[0], p2.pos[1]
m1, m2 = p1.r**2, p2.r**2
n = np.array([x2-x1, y2-y1])
cls.resolveStatically(n, dc, p1, p2)
un = n / np.linalg.norm(n)
ut = np.array([-un[1], un[0]])
v1 = p1.vel
v2 = p2.vel
v1n = np.dot(un, v1)
v1t = np.dot(ut, v1)
v2n = np.dot(un, v2)
v2t = np.dot(ut, v2)
v1n_prime_s = (v1n * (m1 - m2) + 2*m2*v2n) / (m1 + m2)
v2n_prime_s = (v2n * (m2 - m1) + 2*m1*v1n) / (m1 + m2)
v1n_prime = v1n_prime_s * un
v1t_prime = v1t * ut
v2n_prime = v2n_prime_s * un
v2t_prime = v2t * ut
u1 = v1n_prime + v1t_prime
u2 = v2n_prime + v2t_prime
p1.vel = u1
p2.vel = u2
while len(particles) < no_particles:
r = random.randint(10, 20)
x = random.randint(r, width-r)
y = random.randint(r, height-r)
collide = False
for particle in particles:
d = (particle.pos[0] - x)**2 + (particle.pos[1] - y)**2
if d < (r + particle.r)**2:
collide = True
break
if not collide:
particles.append(Particle(x, y, random.randint(10, 20)))
running = True
clock = pygame.time.Clock()
while running:
for event in pygame.event.get():
if event.type == pygame.QUIT:
running = False
screen.fill((255, 255, 255))
for i in range(len(particles)):
particles[i].move()
particles[i].bounce()
for j in range(i + 1, len(particles)):
particles[i].collision(particles[i], particles[j])
particles[i].display()
pygame.display.flip()
clock.tick(tick_speed)
pygame.quit()
quit()
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 make one ball push another, the problem is that when the balls collide the red ball gets into the blue, the red moves towards the left mouse click, the blue one is drawn with the right mouse click. I've noticed that the speed (Velocidad) and
self.xb2-=self.dx+overlapx/(tamaño/2)
self.yb2-=self.dy+overlapy/(tamaño/2)
the magnitude of the divisor, tamaño/2, has a significant role in the problem.
here's the code
import pygame, math, random
pygame.init()
vel = 5
ancho = 600
alto = 500
tamaño=30
color = (250,0,0)
color2= (0,0,250)
FPS=60
negro= 0,0,0
posantes = [[300,250], [- 50, - 50]] #posicion anterior, bola y bola1/ initial position bola , initial position bola1
mouse = [[300,250], [ - 50, - 50]] #posicion mouse, click izquierdo , click derecho
pantalla = pygame.display.set_mode((ancho, alto))
reloj = pygame.time.Clock()
class Bola():
def __init__(self, color, xyb, tamaño):
super().__init__()
pygame.draw.circle(pantalla, color, xyb,tamaño)
def colision(self,xyb,xyb2):
self.colision = False
self.xyb = posantes [0]
self.xyb2 = posantes[1]
self.xb,self.yb = posantes[0][0],posantes[0][1]
self.xb2,self.yb2 = posantes[1][0],posantes[1][1]
dist = math.hypot(self.xb2-self.xb,self.yb2-self.yb)
if dist<tamaño*2:
self.colision =True
else:
self.colision =False
def reaccion(self,xyb,xyb2):
overlapx , overlapy = self.xb-self.xb2, self.yb-self.yb2
if self.colision==True:
self.xb2-=self.dx+overlapx/(tamaño/2)
self.yb2-=self.dy+overlapy/(tamaño/2)
posantes[1]=[self.xb2,self.yb2]
print(" !! reaccion ¡¡")
def moverbola(self, xyb, xy,bi):
self.xyb = posantes[bi]
self.xy = mouse[bi]
self.xb,self.yb = self.xyb[0] , self.xyb[1]
self.x,self.y = self.xy[0] , self.xy[1]
self.dx,self.dy = abs(self.x-self.xb) , abs(self.y-self.yb)
dist = math.hypot(self.x-self.xb,self.y-self.yb)
if dist!= 0:
self.dx = self.dx / dist
self.dy = self.dy / dist
if self.xb < self.x:
self.xb+=vel * self.dx
if self.xb > self.x:
self.xb-=vel * self.dx
if self.yb < self.y:
self.yb+=vel * self.dy
if self.yb > self.y :
self.yb -=vel * self.dy
self.xyb = [self.xb,self.yb]
posantes[bi] = self.xyb
terminar = False
while not terminar:
reloj.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
terminar=True
if event.type == pygame.MOUSEBUTTONDOWN and event.button==1:
mouse[0] = pygame.mouse.get_pos()[0], pygame.mouse.get_pos()[1]
if event.type == pygame.MOUSEBUTTONDOWN and event.button==3:
mouse[1] = pygame.mouse.get_pos()[0],pygame.mouse.get_pos()[1]
posantes[1] = mouse[1]
pantalla.fill(negro)
bola1 = Bola(color2, posantes[1], tamaño)
bola = Bola(color,posantes[0], tamaño)
bola.moverbola(posantes[0], mouse[0],0)
bola.colision(posantes[0],posantes[1])
bola.reaccion(posantes[0],posantes[1])
pygame.display.update()
pygame.quit()
quit()
You misunderstood the concept of object-oriented programming. You must create instances of the Bola class before the application loop, but use them in the loop:
bola1 = Bola(color2, [-50, -50], tamaño)
bola = Bola(color, [300, 250], tamaño)
terminar = False
while not terminar:
bola1.draw()
bola.draw()
Set the attribute Bola in the constructor and use it in the methods, e.g. B. to draw the Bola:
class Bola():
def __init__(self, color, xyb, tamaño):
super().__init__()
self.color = color
self.xyb = xyb
self.tamaño = tamaño
def draw(self):
pos = round(self.xyb[0]), round(self.xyb[1])
pygame.draw.circle(pantalla, self.color, pos, self.tamaño)
Ensure methods and attributes have different names (collision, colision_test). The colision_test method returns the result of the test:
class Bola():
# [...]
def colision_test(self,xyb2):
dist = math.hypot(xyb2[0] - self.xyb[0], xyb2[1] - self.xyb[1])
self.colision = dist < tamaño*2
return (self.colision, dist)
Move the Bola in the reaccion and moverbola methods depending on the arguments of the function and change the attribute in which the position is stored:
class Bola():
# [...]
def reaccion(self, xyb_other, reaction_dist):
dx, dy = xyb_other[0] - self.xyb[0], xyb_other[1] - self.xyb[1]
dist = math.hypot(dx, dy)
if dist > 0:
self.xyb = [self.xyb[0] - dx * reaction_dist / dist, self.xyb[1] - dy * reaction_dist / dist]
def moverbola(self, target):
dx, dy = target[0] - self.xyb[0], target[1] - self.xyb[1]
dist = math.hypot(dx, dy)
if dist > 0:
step = min(dist, vel)
self.xyb = [self.xyb[0] + dx * step / dist, self.xyb[1] + dy * step / dist]
Complete exmaple:
import pygame, math, random
pygame.init()
vel = 5
ancho = 600
alto = 500
tamaño=30
color = (250,0,0)
color2= (0,0,250)
FPS=60
negro= 0,0,0
pantalla = pygame.display.set_mode((ancho, alto))
reloj = pygame.time.Clock()
class Bola():
def __init__(self, color, xyb, tamaño):
super().__init__()
self.color = color
self.xyb = xyb
self.tamaño = tamaño
def draw(self):
pos = round(self.xyb[0]), round(self.xyb[1])
pygame.draw.circle(pantalla, self.color, pos, self.tamaño)
def colision_test(self,xyb2):
dist = math.hypot(xyb2[0] - self.xyb[0], xyb2[1] - self.xyb[1])
self.colision = dist < tamaño*2
return (self.colision, dist)
def reaccion(self, xyb_other, reaction_dist):
dx, dy = xyb_other[0] - self.xyb[0], xyb_other[1] - self.xyb[1]
dist = math.hypot(dx, dy)
if dist > 0:
self.xyb = [self.xyb[0] - dx * reaction_dist / dist, self.xyb[1] - dy * reaction_dist / dist]
def moverbola(self, target):
dx, dy = target[0] - self.xyb[0], target[1] - self.xyb[1]
dist = math.hypot(dx, dy)
if dist > 0:
step = min(dist, vel)
self.xyb = [self.xyb[0] + dx * step / dist, self.xyb[1] + dy * step / dist]
bola1 = Bola(color2, [-50, -50], tamaño)
bola = Bola(color, [300, 250], tamaño)
bola_target = [300, 250]
terminar = False
while not terminar:
reloj.tick(FPS)
for event in pygame.event.get():
if event.type == pygame.QUIT:
terminar=True
if event.type == pygame.MOUSEBUTTONDOWN and event.button==1:
bola_target = event.pos
if event.type == pygame.MOUSEBUTTONDOWN and event.button==3:
bola1.xyb = event.pos
bola.moverbola(bola_target)
collision, dist = bola1.colision_test(bola.xyb)
if collision:
reaccion = (tamaño * 2 - dist)
bp = bola.xyb[:]
bp1 = bola1.xyb[:]
bola.reaccion(bp1, reaccion)
bola1.reaccion(bp, reaccion)
pantalla.fill(negro)
bola1.draw()
bola.draw()
pygame.display.update()
pygame.quit()
quit()
I have a 2d pygame water simulation thingy that I followed a tutorial to make. I also found the answer to this question to fix issues with the tutorial: Pygame water physics not working as intended
I have since been trying to convert this program over to using pyopengl to render things. However, I have been struggling to:
A: Draw the water polygon
B: texture the water polygon with a tiled texture
Here is my (rather poor) attempt at converting this code to pyopengl.
import pygame, random
import math as m
from pygame import *
from OpenGL import *
from OpenGL.GLU import *
from OpenGL.GL import *
pygame.init()
WINDOW_SIZE = (854, 480)
screen = pygame.display.set_mode(WINDOW_SIZE,0,32,DOUBLEBUF|OPENGL) # initiate the window
clock = pygame.time.Clock()
def draw_polygon(polygon_points):
glBegin(GL_POLYGON);
for i in polygon_points:
glVertex3fv(i)
#glEnd()
class surface_water_particle():
def __init__(self, x,y):
self.x_pos = x
self.y_pos = y
self.target_y = y
self.velocity = 0
self.k = 0.04
self.d = 0.08
self.time = 1
def update(self):
x = self.y_pos - self.target_y
a = -(self.k * x + self.d * self.velocity)
if self.y_pos > self.target_y:
self.y_pos -= 0.1
if self.y_pos < self.target_y:
self.y_pos += 0.1
self.velocity = round(self.velocity)
self.y_pos += self.velocity
self.velocity += a
self.time += 1
class water_tile():
def __init__(self, x_start, x_end, y_start, y_end, segment_length):
self.springs = []
self.x_start = x_start
self.y_start = y_start
self.x_end = x_end
self.y_end = y_end - 10
for i in range(abs(x_end - x_start) // segment_length):
self.springs.append(surface_water_particle(i * segment_length + x_start, y_end))
def update(self, spread):
passes = 4 # more passes = more splash spreading
for i in range(len(self.springs)):
self.springs[i].update()
leftDeltas = [0] * len(self.springs)
rightDeltas = [0] * len(self.springs)
for p in range(passes):
for i in range(0, len(self.springs)):
if i > 0:
leftDeltas[i] = spread * (self.springs[i].y_pos - self.springs[i - 1].y_pos)
self.springs[i - 1].velocity += leftDeltas[i]
if i < len(self.springs):
rightDeltas[i] = spread * (self.springs[i].y_pos - self.springs[(i + 1)%len(self.springs)].y_pos)
self.springs[(i + 1)%len(self.springs)].velocity += rightDeltas[i]
for i in range(0, len(self.springs)):
if round (leftDeltas[i],12) == 0 or round (rightDeltas[i],12) == 0:
self.springs[i - 1].y_pos = self.y_end+10
if i > 0:
self.springs[i - 1].y_pos += leftDeltas[i] # you were updating velocity here!
if i < len(self.springs):
self.springs[(i + 1)%len(self.springs)].y_pos += rightDeltas[i]
def splash(self, index, speed):
if index >= 0 and index < len(self.springs):
self.springs[index].velocity = speed
def draw(self):
water_surface = pygame.Surface((abs(self.x_end-self.x_start), abs(self.y_start - self.y_end)), depth=8).convert_alpha()
polygon_points = []
polygon_points.append((self.x_start, self.y_start,0))
for spring in range(len(self.springs)):
polygon_points.append((self.springs[spring].x_pos, self.springs[spring].y_pos,0))
polygon_points.append((self.springs[len(self.springs) - 1].x_pos, self.y_start,0))
draw_polygon(polygon_points)
return water_surface
class water_object:
def __init__(self, x_start, x_end, y_start, y_end, segment_length, x_pos, y_pos):
self.water = water_tile(x_start,x_end,y_start,y_end,segment_length)
self.image = self.water.draw()
self.rect = self.image.get_rect()
self.rect.x = x_pos
self.rect.y = y_pos
def update(self):
self.water.update(0.1)
self.image = self.water.draw()
water_list = [water_object(0,276+16,64,0,16,0,20)]
while True:
screen.fill((0,0,0))
for water in water_list:
gluPerspective(45, (WINDOW_SIZE[0]/WINDOW_SIZE[1]), 0.1, 50.0)
glClear(GL_COLOR_BUFFER_BIT|GL_DEPTH_BUFFER_BIT)
water.update()
#screen.blit(water.image, (water.rect.x,water.rect.y))
#water_test.x_start = water_test.x_start + 1
#if random.randint(0,8) == 1:
#water_test.splash(random.randint(0, len(water_test.springs) - 1),2)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
if event.type == MOUSEBUTTONDOWN:
print (len(water.water.springs))
water.water.splash(random.randint(0, len(water.water.springs) - 1),50)
pygame.display.update()
clock.tick(60)
However, despite my attempt, I couldnt get anything to display on screen at all. How can I fix this/how can I attain the 2 things I have been struggling with?
You cannot draw an OpenGL primitive to a pygame.Surface. Anyway there is no need to do so.
For the best performance, directly draw to the default framebuffer (window).
Since you want to draw a line, you have to use a Line primitive type. GL_POLYGON would draw a filed convex polygon. Use the primitive type GL_LINE_STRIP:
def draw_polygon(polygon_points):
glBegin(GL_LINE_STRIP)
for pt in polygon_points:
glVertex2f(*pt)
glEnd()
Before you draw the line, ser the current color by glColor:
glColor3f(0, 0, 1)
draw_polygon(polygon_points)
The vertex coordinates of the lie are specified in window space. Hence you have to setup an Orthographic projection rather than a Perspective projection. Specify the current matrix by [glMatrixMode] and set the projection matrix by glOrtho. Since the matrix operations do not set a matrix, but multiply the current matrix by the specified matrix, I recommend to load the identity matrix before (glLoadIdentity):
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, WINDOW_SIZE[0], WINDOW_SIZE[1], 0, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
Before you draw the line you have to clear the framebuffer by glClear. The clear color can be defined by glClearColor:
glClearColor(1, 1, 1, 1)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
Complete example:
import pygame
from OpenGL import *
from OpenGL.GL import *
def draw_polygon(surf_rect, polygon_points):
glBegin(GL_LINE_STRIP)
#glBegin(GL_TRIANGLE_STRIP)
for pt in polygon_points:
glVertex2f(*pt)
glVertex2f(pt[0], surf_rect.height)
glEnd()
class WaterParticle():
def __init__(self, x, y):
self.x, self.y = x, y
self.target_y = y
self.velocity = 0
self.k = 0.04
self.d = 0.08
def update(self):
x = self.y - self.target_y
a = -(self.k * x + self.d * self.velocity)
#self.p[1] += -0.1 if x > 0 else 0.1 if x < 0 else 0
self.y += self.velocity
self.velocity += a
class Water():
def __init__(self, x_start, x_end, y_start, segment_length, passes, spread):
n = abs(x_end - x_start + segment_length - 1) // segment_length + 1
self.particles = [WaterParticle(i * segment_length + x_start, y_start) for i in range(n)]
self.passes = passes
self.spread = spread
def update(self):
for particle in self.particles:
particle.update()
left_deltas = [0] * len(self.particles)
right_deltas = [0] * len(self.particles)
for _ in range(self.passes):
for i in range(len(self.particles)):
if i > 0:
left_deltas[i] = self.spread * (self.particles[i].y - self.particles[i - 1].y)
self.particles[i - 1].velocity += left_deltas[i]
if i < len(self.particles)-1:
right_deltas[i] = self.spread * (self.particles[i].y - self.particles[i + 1].y)
self.particles[i + 1].velocity += right_deltas[i]
for i in range(len(self.particles)):
if i > 0:
self.particles[i-1].y += left_deltas[i]
if i < len(self.particles) - 1:
self.particles[i+1].y += right_deltas[i]
def splash(self, index, speed):
if index > 0 and index < len(self.particles):
self.particles[index].velocity += speed
def draw(self, surf_rect):
polygon_points = []
for spring in range(len(self.particles)):
polygon_points.append((self.particles[spring].x, self.particles[spring].y))
glColor3f(0, 0, 1)
draw_polygon(surf_rect, polygon_points)
pygame.init()
window = pygame.display.set_mode((640, 480), pygame.DOUBLEBUF | pygame.OPENGL)
clock = pygame.time.Clock()
glMatrixMode(GL_PROJECTION)
glLoadIdentity()
glOrtho(0, *window.get_size(), 0, -1, 1)
glMatrixMode(GL_MODELVIEW)
glLoadIdentity()
glClearColor(1, 1, 1, 1)
water_line_y = window.get_height() // 2
water = Water(0, window.get_width(), window.get_height() // 2, 3, 8, 0.025)
while True:
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit()
if event.type == pygame.MOUSEBUTTONDOWN:
velocity = water_line_y - event.pos[1]
if velocity > 0:
index = int(len(water.particles) * event.pos[0] / window.get_width())
water.splash(index, velocity)
water.update()
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT)
water.draw(window.get_rect())
pygame.display.flip()
clock.tick(50)
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()
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()