在 python py-game 中碰撞时粒子重叠

Particles overlapping when colliding in python py-game

]当 运行 模拟时,粒子可能会重叠并有点异常。我认为因为它是一个离散模拟,当检测到碰撞时粒子已经重叠,所以计算最终速度的部分无法正常工作。

我为粒子和屏幕边界解决了同样的问题,但我不知道如何处理粒子-粒子碰撞。任何帮助将不胜感激

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

问题是您在单次迭代中多次检查和解决同一个球的碰撞。

由于方向已经被“反射”一次,所以这些点积,

v1n = np.dot(un, v1)
v1t = np.dot(ut, v1)

v2n = np.dot(un, v2)
v2t = np.dot(ut, v2) 

在下一个碰撞决议中产生相反的方向,这导致球相互移动。

每次球碰撞时都会向系统添加“能量”,因此,

p1.r**2, p2.r**2
2*m2*v2n, 2*m1*v1n

这导致了你提到的异常。

解决方法是简单地检查并解决一次冲突:

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

正如 Rabbid76 指出的那样,随着粒子数量的增加,这会失败。如果您首先静态解决碰撞,一切都会正常,因为碰撞已经解决并且您的动态解决代码将相应地设置速度。

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

完整代码:

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