英文:
How do I account for a particle at rest on the ground in a gravity particle simulation?
问题
Here's the translation of the text you provided:
"最近,我决定从头开始编写一个离散碰撞模拟程序,以帮助学习Python并为个人乐趣。我编写了这个程序,通过将每次离散时间更新后的图形保存为一个文件夹中的图像,然后编译所有这些图像(使用ffmpeg),从而生成视频。起初,我想看看如果我模拟一个球在受重力影响下在一个盒子中运动会发生什么。我知道答案应该是什么样的,因为我小时候玩过弹跳球。然而,每当球靠近其弹跳的尽头时,它会以固定速度剪切穿过地板,而不是停在地板上休息,我无论如何都无法弄清楚为什么。
我还尝试在模拟中添加另一个粒子,并去除了重力,以便这些粒子在自由空间中弹跳,但有时当粒子碰撞时会出现相同的问题。它们会粘在一起,而不是弹开,就像单个球会粘在地板上一样。
这是我使用的代码。我尽力对其进行了注释。我不是一个非常熟练的编程者,所以如果有常规错误,我深感抱歉。
我认为问题出在“handleBoxCollision()”函数中。我最好的猜测是,在小数值下,碰撞后的速度不足以在下一帧将粒子推出,因此它会一次又一次地在墙上保持固定速度,并且永远无法出来。我已经尝试将速度计算更改为绝对值(因为当球撞击地面时,它的速度为负,然后切换为正),但是当球应该休息时,它仍然会穿过地板。我使用的代码如下,以及一个指向视频的Discord链接。有人有什么见解吗?
视频链接:https://cdn.discordapp.com/attachments/368098721994506240/1104520524488527883/video.webm"
Please note that the code portions remain untranslated, as per your request.
英文:
Recently I decided to write a discrete collision simulation from scratch to help learn python and for personal enjoyment. I wrote this program to create a video by saving each graph after a discrete time update as an image in a folder, then compiling all those images together (using ffmpeg) resulting in a video. At first I wanted to see what would happen if I simulated a ball in a box under the influence of gravity. I know what the answer should have come out to look like, I've played with bouncy balls as a kid. However whenever the ball gets near the end of its bouncing, it will start to clip through the floor at a stuck velocity rather than coming to a rest on the floor and I can not for the life of me figure out why.
I also tried adding in another particle to the simulation and took out gravity so that these particles bounce around in free space, however the same problem will occur sometimes when the particles collide. They stick together rather than bouncing off, much like how a single ball will stick into the floor.
This is the code that I used. I tried to comment it as best as I could. I'm not a very proficient coder so I apologize if there are conventional errors.
I think the problem is in the "handleBoxCollision()" function. My best guess is that at small numbers the velocity after collision isn't enough to get the particle out on the next frame, so it stays stuck in the wall getting velocity flipped again and again and again and it can never get out. I have tried changing the velocity calculation to an absolute value (since when the ball hits the ground it has negative velocity, then switches to positive) but the ball will still continue to go through the floor when it is supposed to be at rest. The code I used is included below, as well as a discord link that goes to the video. where Does anyone have any insight?
video: 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')
答案1
得分: 0
以下是您要求的代码部分的翻译:
问题在于您允许particle.position[1]
移动到box.bottom + particle.radius
以下。然后,在handleBoxCollision()
反向并减小(因为cor < 1
)该方向的速度后,下一个时刻不会将粒子移回到框的边界内,因此它会继续在边缘粘附的同时反向。如果将cor = 1
设置为速度永远不会减小,它将按预期弹跳。
我做了两个更改如下:
- 在
handleBoxCollision()
内部,我添加了以下代码行:
if p.bottom <= box.bottom:
p.position[1] = box.bottom + p.radius
这将始终强制粒子回到框内。我只处理了底部边缘,因为对于您的重力模拟来说,这是最相关的。根据需要添加类似的边缘重置以解决其他边缘的问题。根据检查最后的50-60张图像,我没有制作视频,但似乎已解决了该问题。
另一种解决方法是在更新中处理此问题,并通过将box.side +/- particle.radius
设置为粒子位置的硬限制来防止粒子位置太靠近边缘。
- 我将粒子的
left/right/top/bottom
属性更改为只有getter的属性,并删除了所有设置p.left/right/top/bottom的行。这简化了代码,现在您可以根据需要更改位置,而无需每次都更新这些边界。
如果您需要进一步的帮助或解释,请告诉我。
英文:
The problem is that you're allowing particle.position[1]
to move below box.bottom + particle.radius
. Then after handleBoxCollision()
reverses AND reduces (due to 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
so that velocity is never reduced, it will keep bouncing as expected.
I made two changes below:
- Within
handleBoxCollision()
I added the lines:
if p.bottom <= box.bottom:
p.position[1] = box.bottom + p.radius
This will always force the particle back inside of the box. I only
handled the bottom edge as it is the most relevant for your gravity simulation. Adding similar resets for the other edges is up to you.
I didn't make a video, but based on checking the last 50-60 images, it seems to have resolved the issue.
An alternative solution would be to handle this within update and prevent the particle position from getting too close to the edges in the first place, by setting box.side +/- particle.radius as a hard limit of particle.position.
- I changed the particle.left/right/top/bottom attributes into properties with getters only and removed all lines where p.left/right/top/bottom was being set. This simplifies things as now you can just change the position as needed without having to have additional lines to update those boundaries every time.
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')
Suggestions for other possible improvements:
-
Update
handleBoxCollision()
to take the particle and box as arguments. This will make it easier to support multiple particles and won't rely on global variables. It might work best as a method of the Box class, then you just pass it the particle to check collisions for. Or make it a method of the particle class and just pass the box in; but that feels backwards for some reason. -
Likewise,
update()
should probably be a method of the Particle class.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论