Lennard-Jones potential simulation - python

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()

Related

How can I connect two points with a series of circles?

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()

Particles overlapping when colliding in python py-game

]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()

Converting pygame 2d water ripple to pyOpenGL

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)

Python/ Pygame : 2d angular momentum / inertia

my first ever Python program has hit a block I don't think I have the knowledge to solve myself.
It's a controllable spaceship on a 2d surface, I want to add momentum / inertia
I have it so the ship keeps travelling on the vector it previously was, when the engine is stopped.
However I can only get it to 'snap' to the new vector it rotates to face instantly.
What I want to happen is that inertia vector slowly aligns with the new pointing vector as it accelerates- like rotational acceleration? ( I'm not too hot on the math ) - I can rotate the inertia vector , but I would need to compare it somehow with the new pointing vector , and modify it based upon their difference?
if anyone could advise as to how I might start to approach this, that would be great - I suspect I coming at this from completely the wrong way.
Heres some of the code ( be gentle please!)
the sprite used is this : - ship.png
import pygame
import sys
from math import sin, cos, pi, atan2
from pygame.locals import *
import random
from random import randint
from pygame.math import Vector2
import operator
"""solar system generator"""
"""set screen size and center and some global namespace colors for ease of use"""
globalalpha = 255
screenx = int(1200)
screeny = int(700)
centerx = int(screenx / 2)
centery = int(screeny / 2)
center = (centerx, centery)
black = ( 0, 0, 0)
white = (255, 255, 255)
red = (209, 2, 22)
TRANSPARENT = (255,0,255)
numstars = 150
DISPLAYSURF = pygame.display.set_mode((screenx, screeny), 0, 32)
clock = pygame.time.Clock()
globaltimefactor = 1
shipimage = pygame.image.load('ship.png').convert()
DISPLAYSURF.fill(black)
screen_rect = DISPLAYSURF.get_rect()
class Playership(pygame.sprite.Sprite):
def __init__(self):
super().__init__()
self.imageorig = pygame.image.load('ship.png').convert_alpha()
self.startpos = (screen_rect.center)
self.image = self.imageorig.copy()
self.rect = self.imageorig.get_rect(center=self.startpos)
self.angle = 0
self.currentposx = 600
self.currentposy = 350
self.tuplepos = (self.currentposx, self.currentposy)
self.speed = 1
self.rotatespeed = 1.5
self.initialvec = (600, 0)
self.destination = 0
self.anglechange = 0
self.currentspeed = 0
self.maxspeed = 5
self.engineon = False
self.newvec = (600, 0)
self.newdestination = 0
self.acceleration = 0.015
self.inertiaspeed = 0
self.transitionalvec = self.initialvec
def get_angleafterstopping(self):
newvec = self.initialvec
self.newvec = newvec
def get_destinationafterstopping(self):
x_dist = self.newvec[0] - self.tuplepos[0]
y_dist = self.newvec[1] - self.tuplepos[1]
self.newdestination = atan2(-y_dist, x_dist) % (2 * pi)
def get_destination(self):
x_dist = self.initialvec[0] - self.tuplepos[0]
y_dist = self.initialvec[1] - self.tuplepos[1]
self.destination = atan2(-y_dist, x_dist) % (2 * pi)
def moveship(self):
if self.engineon is True:
self.currentspeed = self.currentspeed + self.acceleration
if self.currentspeed > self.maxspeed:
self.currentspeed = self.maxspeed
elif self.currentspeed < 0:
self.currentspeed = 0
self.inertiaspeed = self.currentspeed
elif self.engineon is False:
self.currentposx = self.currentposx + (cos(self.newdestination) * self.inertiaspeed * globaltimefactor)
self.currentposy = self.currentposy - (sin(self.newdestination) * self.inertiaspeed * globaltimefactor)
self.tuplepos = (self.currentposx, self.currentposy)
self.rect.center = self.tuplepos
return
self.get_destination()
self.currentposx = self.currentposx + (cos(self.destination) * self.currentspeed * globaltimefactor)
self.currentposy = self.currentposy - (sin(self.destination) * self.currentspeed * globaltimefactor)
self.tuplepos = (self.currentposx, self.currentposy)
self.rect.center = self.tuplepos
def rotateship(self, rotation):
self.anglechange = self.anglechange - (rotation * self.rotatespeed * globaltimefactor)
self.angle += (rotation * self.rotatespeed * globaltimefactor)
self.image = pygame.transform.rotate(self.imageorig, self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
initialvec = self.tuplepos + Vector2(0, -600).rotate(self.anglechange * globaltimefactor)
initialvec = int(initialvec.x), int(initialvec.y)
self.initialvec = initialvec
myship = Playership()
all_sprites_list = pygame.sprite.Group()
all_sprites_list.add(myship)
firsttimedone = False
def main():
done = False
while not done:
keys_pressed = pygame.key.get_pressed()
if keys_pressed[pygame.K_LEFT]:
myship.rotateship(1)
if keys_pressed[pygame.K_RIGHT]:
myship.rotateship(-1)
if keys_pressed[pygame.K_UP]:
myship.engineon = True
myship.moveship()
for event in pygame.event.get():
if event.type == pygame.QUIT:
pygame.quit(); sys.exit();
if event.type == pygame.KEYUP:
if event.key == pygame.K_UP:
myship.engineon = False
myship.currentspeed = 0
myship.get_angleafterstopping()
myship.get_destinationafterstopping()
DISPLAYSURF.fill(black)
all_sprites_list.update()
all_sprites_list.draw(DISPLAYSURF)
pygame.draw.line(DISPLAYSURF, white, (myship.tuplepos), (myship.initialvec))
pygame.draw.line(DISPLAYSURF, red, (myship.tuplepos), (myship.newvec))
pygame.display.flip()
if myship.engineon is False:
myship.moveship()
clock.tick(50)
pygame.display.set_caption("fps: " + str(clock.get_fps()))
if __name__ == '__main__':
pygame.init()
main()
pygame.quit(); sys.exit();
EDIT :
I fixed it : just required a better understanding of vectors
ship starts off with acceleration and velocity both stated as vectors.
self.position = vec(screenx / 2, screeny / 2)
self.vel = vec(0, 0)
self.acceleration = vec(0, -0.2) # The acceleration vec points upwards from the starting ship position
rotating the ship rotates that vector in place
self.acceleration.rotate_ip(self.angle_speed)
self.angle += self.angle_speed
self.image = pygame.transform.rotate(self.imageorig, -self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
accelerating is this :
self.vel += self.acceleration * self.enginepower * globaltimefactor
updating position :
self.position += self.vel
self.rect.center = self.position
I was making it harder than it needed to be, velocity needed to be constant until acted upon by the rotated acceleration vector. I didn't know how to add vectors together etc.
I fixed it : just required a better understanding of vectors
ship starts off with acceleration and velocity both stated as vectors.
self.position = vec(screenx / 2, screeny / 2)
self.vel = vec(0, 0)
self.acceleration = vec(0, -0.2) # The acceleration vec points upwards from the starting ship position
rotating the ship rotates that vector in place
self.acceleration.rotate_ip(self.angle_speed)
self.angle += self.angle_speed
self.image = pygame.transform.rotate(self.imageorig, -self.angle)
self.rect = self.image.get_rect(center=self.rect.center)
accelerating is this :
self.vel += self.acceleration * self.enginepower * globaltimefactor
updating position :
self.position += self.vel
self.rect.center = self.position
I was making it harder than it needed to be, velocity needed to be constant until acted upon by the rotated acceleration vector. I didn't know how to add vectors together etc.

Circle objects register a collision, but they are not touching

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

Categories

Resources