如何在重力粒子模拟中考虑静止在地面上的粒子?

问题描述 投票:0回答:1

最近决定从零开始写一个离散碰撞模拟,以帮助学习python和个人欣赏。我编写这个程序来创建视频,方法是在离散时间更新后将每个图形保存为文件夹中的图像,然后将所有这些图像编译在一起(使用 ffmpeg)生成视频。起初我想看看如果我在重力的影响下模拟一个盒子里的球会发生什么。我知道答案应该是什么样子,我小时候玩过弹力球。然而,每当球接近其弹跳的终点时,它就会开始以固定的速度穿过地板,而不是停在地板上,我终究无法弄清楚为什么。

我也试过在模拟中加入另一个粒子并去掉重力,让这些粒子在自由空间中弹跳,但有时粒子碰撞时也会出现同样的问题。它们粘在一起而不是弹开,就像一个球会粘在地板上一样。

这是我使用的代码。我试着尽可能地评论它。我不是一个非常熟练的编码器,所以如果有常规错误,我深表歉意。

我认为问题出在“handleBoxCollision()”函数中。我最好的猜测是,在少量情况下,碰撞后的速度不足以让粒子在下一帧中出来,所以它一直卡在墙上,速度一次又一次地翻转,它永远无法出去。我曾尝试将速度计算更改为绝对值(因为当球撞击地面时它具有负速度,然后切换为正速度)但是球在应该静止时仍会继续穿过地板。下面包含我使用的代码,以及指向视频的不和谐链接。谁有任何见解?

视频:https://cdn.discordapp.com/attachments/368098721994506240/1104520524488527883/video.webm

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import math
import os

#Class defining a particle used in the simulation
class Particle:
    def __init__(self, mass, position, velocity, acceleration):
        
        self.mass = mass
        self.radius = math.sqrt(self.mass/(math.pi*1.5))
        self.position = position
        self.velocity = velocity
        self.acceleration = acceleration
        self.KE = (1/2)*self.mass*np.dot(self.velocity,self.velocity)
        
        self.left = position[0]-self.radius
        self.right = position[0]+self.radius
        self.top = position[1]+self.radius
        self.bottom = position[1]-self.radius
        
#just defining the box that the simulation takes place in
class Box:
    def __init__(self):
        self.left = -10
        self.right = 10
        self.bottom = -10
        self.top = 10
        
#Function that detects if there is a colision between the particle and the box
#This is where i think theres a problem but cant for the life of me figure out what it is
def handleBoxCollision():
    #cor is the coefficient of restitution
    cor = 0.8
    if p.left <= box.left or p.right >= box.right:
        p.velocity[0]=-cor*p.velocity[0]
        
    if p.bottom <= box.bottom or p.top >= box.top:
        p.velocity[1]=-cor*p.velocity[1]
        
#Since this is a discreet colission simulation this function is for updating the state of the simulation
def update(dt):
    p.velocity = p.velocity+(p.acceleration*dt)
    p.position = p.position+(p.velocity*dt)
    p.left = p.position[0]-p.radius
    p.right = p.position[0]+p.radius
    p.top = p.position[1]+p.radius
    p.bottom = p.position[1]-p.radius
    
    handleBoxCollision()

#Because I run this simulation many times I first delete all the contents in my directory
#Then as simulation runs it saves each updated graph as a frame. Compile all frames into a video
#I took out the actual directory on my computer for privacy
dir = 'C:/PathToImageFile'
for f in os.listdir(dir):
    os.remove(os.path.join(dir, f))

#Initial mass, position, velocity and acceleration
mass = 10
position = np.array([0,0])
velocity = np.array([5,0])
acceleration = np.array([0,-9.8])

#time step = 1/framerate
dt = 1/60

p = Particle(mass, position, velocity, acceleration)
box = Box()

#Run this loop for however many frames I want. In this case 600
for i in range(600):
    figure, axes = plt.subplots()
    
    update(dt)
    
    #left of box
    plt.plot([-10,-10],[-10,10],color='black')
    #right of box
    plt.plot([10,10],[-10,10],color='black')
    #top of box
    plt.plot([-10,10],[10,10],color='black')
    #bottom of box
    plt.plot([-10,10],[-10,-10],color='black')
    
    cc = plt.Circle((p.position[0] ,p.position[1]), p.radius)

    plt.scatter(p.position[0],p.position[1])
    plt.xlim(-11,11)
    plt.ylim(-11,11)
    plt.text(-10,-10.7,"time="+str(i*dt))
    plt.text(-10,10.3,"velocity="+str(p.velocity[1]))
    axes=plt.gca()
    axes.add_artist(cc)
    axes.set_aspect(1)
    
    figure.savefig('/PathToImageFile'+str(i)+'.png')
    plt.close('all')
python simulation physics-engine
1个回答
0
投票

问题是您允许

particle.position[1]
移动到
box.bottom + particle.radius
以下。然后在
handleBoxCollision()
反转并减少之后(由于
cor
< 1) the velocity in that direction, the next tick doesn't move the particle back inside of the box bounds, so it just keeps reversing directions while stuck along the edge. If you set
cor = 1
因此速度永远不会降低,它将按预期保持弹跳。

我在下面做了两个更改:

  1. handleBoxCollision()
    内我添加了以下几行:
    if p.bottom <= box.bottom:
        p.position[1] = box.bottom + p.radius

这将始终迫使粒子回到盒子内部。我只是 处理底部边缘,因为它与您的重力模拟最相关。为其他边缘添加类似的重置取决于您。
我没有制作视频,但根据检查最后 50-60 张图像,它似乎已经解决了这个问题。 另一种解决方案是在更新中处理这个问题,并通过将 box.side +/- particle.radius 设置为 particle.position 的硬限制来防止粒子位置首先太靠近边缘。

  1. 我将 particle.left/right/top/bottom 属性更改为仅具有 getter 的属性,并删除了设置 p.left/right/top/bottom 的所有行。这简化了事情,因为现在您可以根据需要更改位置,而不必每次都使用额外的行来更新这些边界。
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.cm as cm
import math
import os

#Class defining a particle used in the simulation
class Particle:
    def __init__(self, mass, position, velocity, acceleration):
        
        self.mass = mass
        self.radius = math.sqrt(self.mass/(math.pi*1.5))
        self.position = position
        self.velocity = velocity
        self.acceleration = acceleration
        self.KE = (1/2)*self.mass*np.dot(self.velocity,self.velocity)
        
    # Use properties for the particle boundaries
    @property
    def left(self):
        return self.position[0] - self.radius
    
    @property
    def right(self):
        return self.position[0] + self.radius
    
    @property
    def top(self):
        return self.position[1] + self.radius
    
    @property
    def bottom(self):
        return self.position[1] - self.radius
        
#just defining the box that the simulation takes place in
class Box:
    def __init__(self):
        self.left = -10
        self.right = 10
        self.bottom = -10
        self.top = 10
        
#Function that detects if there is a colision between the particle and the box
#This is where i think theres a problem but cant for the life of me figure out what it is
def handleBoxCollision():
    #cor is the coefficient of restitution
    cor = 0.8
    if p.left <= box.left or p.right >= box.right:
        p.velocity[0]=-cor*p.velocity[0]
        
    if p.bottom <= box.bottom or p.top >= box.top:
        p.velocity[1]=-cor*p.velocity[1]

    if p.bottom <= box.bottom:
        p.position[1] = box.bottom + p.radius
        
#Since this is a discreet colission simulation this function is for updating the state of the simulation
def update(dt):
    p.velocity = p.velocity+(p.acceleration*dt)
    p.position = p.position+(p.velocity*dt)
    
    handleBoxCollision()

#Because I run this simulation many times I first delete all the contents in my directory
#Then as simulation runs it saves each updated graph as a frame. Compile all frames into a video
#I took out the actual directory on my computer for privacy
dir = './particle_image'
for f in os.listdir(dir):
    os.remove(os.path.join(dir, f))

#Initial mass, position, velocity and acceleration
mass = 10
position = np.array([0,0])
velocity = np.array([5,0])
acceleration = np.array([0,-9.8])

#time step = 1/framerate
dt = 1/60

p = Particle(mass, position, velocity, acceleration)
box = Box()

#Run this loop for however many frames I want. In this case 600
for i in range(600):
    figure, axes = plt.subplots()
    
    update(dt)
    
    #left of box
    plt.plot([-10,-10],[-10,10],color='black')
    #right of box
    plt.plot([10,10],[-10,10],color='black')
    #top of box
    plt.plot([-10,10],[10,10],color='black')
    #bottom of box
    plt.plot([-10,10],[-10,-10],color='black')
    
    cc = plt.Circle((p.position[0] ,p.position[1]), p.radius)

    plt.scatter(p.position[0],p.position[1])
    plt.xlim(-11,11)
    plt.ylim(-11,11)
    plt.text(-10,-10.7,"time="+str(i*dt))
    plt.text(-10,10.3,"velocity="+str(p.velocity[1]))
    axes=plt.gca()
    axes.add_artist(cc)
    axes.set_aspect(1)
    
    figure.savefig('./particle_image/'+str(i)+'.png')
    plt.close('all')

其他可能的改进建议:

  1. 更新

    handleBoxCollision()
    以粒子和盒子作为参数。这将使支持多个粒子变得更容易,并且不会依赖于全局变量。它可能最适合作为 Box 类的方法,然后您只需将粒子传递给它以检查碰撞。或者把它做成粒子类的一个方法,把盒子传进去就可以了;但出于某种原因感觉倒退了。

  2. 同理,

    update()
    应该是Particle类的一个方法。

© www.soinside.com 2019 - 2024. All rights reserved.