python中运动的粒子之间的弹性碰撞:为什么动能不守恒?

时间:2018-08-02 11:18:02

标签: python pygame collision-detection

我正在尝试在pygame中编写粒子模拟的代码,但是在编码粒子之间的碰撞时遇到了麻烦。所有碰撞都是弹性的,因此应保持动能。但是,我遇到两个主要问题:

  1. 颗粒加速并加速直至失去控制
  2. 颗粒聚集在一起并停止移动

我非常感谢能帮助解决这些问题的见识。

我使用此文档(http://www.vobarian.com/collisions/2dcollisions2.pdf)帮助计算了碰撞后粒子的新速度。所有粒子的质量相同,因此在计算中我忽略了它们的质量。 到目前为止,这是我的代码:

import pygame
pygame.init()
import random
import numpy

HEIGHT = 500
WIDTH = 500
NUM_BALLS = 2


#setup
win = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("Simulation")

class Particle(object):
    def __init__(self, x, y , radius):
        self.x = x
        self.y = y
        self.radius = radius
        self.xvel = random.randint(1,5)
        self.yvel = random.randint(1,5)

    def Draw_circle(self):
        pygame.draw.circle(win, (255,0,0), (self.x, self.y), self.radius)


def redrawGameWindow():
    win.fill((0,0,0))
for ball in balls:
    ball.Draw_circle()

pygame.display.update()


balls = []    
for turn in range(NUM_BALLS):
balls.append(Particle(random.randint(20,WIDTH-20),random.randint(20,HEIGHT-20),20))


run = True
while run:

for event in pygame.event.get():
    if event.type == pygame.QUIT:
        run = False

for ball in balls:
     if (ball.x + ball.radius) <= WIDTH or (ball.x - ball.radius) >= 0:
       ball.x += ball.xvel
     if (ball.x + ball.radius) > WIDTH or (ball.x - ball.radius) < 0:
         ball.xvel *= -1
         ball.x += ball.xvel
     if (ball.y + ball.radius) <= HEIGHT or (ball.y - ball.radius) >= 0:
         ball.y += ball.yvel
     if (ball.y + ball.radius) > HEIGHT or (ball.y - ball.radius) < 0:
         ball.yvel *= -1
         ball.y += ball.yvel

#code for collision between balls
for ball in balls:
    for i in range(len(balls)):
        for j in range (i+1, len(balls)):
            d = (balls[j].x-balls[i].x)**2 + (balls[j].y-balls[i].y)**2
            if d < (balls[j].radius + balls[i].radius)**2:
                print("collision")
                n  = (balls[i].x-balls[j].x, balls[i].y-balls[j].y) #normal vector
                un = n/ numpy.sqrt((balls[i].x-balls[j].x)**2 + (balls[i].y-balls[j].y)**2) #unit normal vector
                ut = numpy.array((-un[1],un[0])) #unit tangent vector
                u1n = numpy.vdot(un, (balls[j].xvel, balls[j].yvel)) #initial velocity in normal direction for first ball
                v1t = numpy.vdot(ut, (balls[j].xvel, balls[j].yvel)) #initial velocity in tangent direction (remains unchanged)
                u2n = numpy.vdot(un, (balls[i].xvel, balls[i].yvel)) #second ball
                v2t = numpy.vdot(ut, (balls[i].xvel, balls[i].yvel))
                v1n = u2n
                v2n = u1n
                v1n *= un
                v2n *= un
                v1t *= ut
                v2t *= ut
                v1 = v1n + v1t #ball 1 final velocity
                v2 = v2n + v2t #ball 2 final velocity
                balls[j].xvel = int(numpy.vdot(v1, (1,0))) #splitting final velocities into x and y components again
                balls[j].yvel = int(numpy.vdot(v1, (0,1)))
                balls[i].xvel = int(numpy.vdot(v2, (1,0)))
                balls[i].yvel = int(numpy.vdot(v2, (0,1)))

redrawGameWindow()
pygame.time.delay(20)

pygame.quit()

2 个答案:

答案 0 :(得分:1)

v1 = v1n + v1t中,v1n是向量,而v1t是标量。

因此,值v1t被添加到v1n向量的两个分量中,从而导致错误。

您想将切线速度添加为矢量,因此应该像对法向速度那样进行操作:

v1n *= un
v2n *= un
v1t *= ut
v2t *= ut

请注意,对于两个截然不同的事物(向量及其范数)使用相同的变量,会使您的代码更难以理解,并且可能导致错误-就像刚才那样。您可能应该以更一致的方式重命名变量,以使差异更清晰。

此外,ut必须是一个numpy数组,但是您将其设为元组。考虑区别:

带有元组

>>> t = (3, 4)
>>> 3*t
(3, 4, 3, 4, 3, 4)

使用np.array:

>>> t = np.array((3, 4))
>>> 3*t
array([ 9, 12])

所以,您必须更改

ut = (-un[1],un[0])

进入

ut = numpy.array((-un[1],un[0]))

答案 1 :(得分:0)

即使您始终检查是否存在碰撞,物体粘在一起的问题还是很简单的,它真正能够识别出的时间就是圆圈已经过去但彼此稍微接触了。圆圈现在反弹并移动了一点。但是,它们还没有完全移动到完全不会碰到的地方,这意味着弹跳再次出现,并且循环播放。我编写了可解决此问题的代码,但是您必须更改bounce函数并使用您的代码,因为我的代码不是那么完美,但可以起作用。

import pygame
pygame.init()
import random
import numpy

HEIGHT = 500
WIDTH = 500
NUM_BALLS = 2


#setup
win = pygame.display.set_mode((WIDTH,HEIGHT))
pygame.display.set_caption("Simulation")

class Particle(object):
    def __init__(self, x, y , radius):
        self.x = x
        self.y = y
        self.radius = radius
        self.xvel = random.randint(1,5)
        self.yvel = random.randint(1,5)
        self.colideCheck = []
    def Draw_circle(self):
        pygame.draw.circle(win, (255,0,0), (int(self.x), int(self.y)), self.radius)


def redrawGameWindow():
    win.fill((0,0,0))
    for ball in balls:
        ball.Draw_circle()
    pygame.display.update()
def bounce(v1,v2, mass1 = 1, mass2 = 1):




    #i Tried my own bouncing mechanism, but doesn't work completly well, add yours here




    multi1 = mass1/(mass1+mass2)
    multi2 = mass2/(mass1+mass2)
    deltaV2 = (multi1*v1[0]-multi2*v2[0],multi1*v1[1]-multi2*v2[1])
    deltaV1 = (multi2*v2[0]-multi1*v1[0],multi2*v2[1]-multi1*v1[1])
    print("preVelocities:",v1,v2)
    return deltaV1,deltaV2
def checkCollide(circ1Cord,circ1Rad,circ2Cord,circ2Rad):
    tryDist = circ1Rad+circ2Rad
    actualDist = dist(circ1Cord,circ2Cord)
    if dist(circ1Cord,circ2Cord) <= tryDist:
        return True
    return False
def dist(pt1,pt2):
    dX = pt1[0]-pt2[0]
    dY = pt1[1]-pt2[1]
    return (dX**2 + dY**2)**0.5
balls = []  
for turn in range(NUM_BALLS):
    balls.append(Particle(random.randint(60,WIDTH-60),random.randint(60,HEIGHT-60),60))


run = True
while run:

    for event in pygame.event.get():
        if event.type == pygame.QUIT:
            run = False
    for ball in balls:
         if (ball.x + ball.radius) <= WIDTH and (ball.x - ball.radius) >= 0:
             ball.x += ball.xvel
         else:
             ball.xvel *= -1
             ball.x += ball.xvel
         if (ball.y + ball.radius) <= HEIGHT and (ball.y - ball.radius) >= 0:
             ball.y += ball.yvel
         else:
             ball.yvel *= -1
             ball.y += ball.yvel
    for ball in balls:
        for secBallCheck in balls:
            if secBallCheck not in ball.colideCheck and ball!= secBallCheck and checkCollide((ball.x,ball.y),ball.radius,(secBallCheck.x,secBallCheck.y),secBallCheck.radius):
                print("COLLIDE")
                vel1,vel2 = bounce((ball.xvel,ball.yvel),(secBallCheck.xvel,secBallCheck.yvel))
                print(vel1,vel2)
                ball.xvel = vel1[0]
                ball.yvel = vel1[1]
                ball.colideCheck.append(secBallCheck)
                secBallCheck.xvel = vel2[0]
                secBallCheck.yvel = vel2[1]
                secBallCheck.colideCheck.append(ball)
            elif not checkCollide((ball.x,ball.y),ball.radius,(secBallCheck.x,secBallCheck.y),secBallCheck.radius):
                if ball in secBallCheck.colideCheck:
                    secBallCheck.colideCheck.remove(ball)
                    ball.colideCheck.remove(secBallCheck)
    redrawGameWindow()
    pygame.time.delay(20)

pygame.quit()