圆与圆的碰撞(物理),但是当很多圆堆积起来时就会出问题

发布于 2025-01-16 19:32:09 字数 5376 浏览 0 评论 0原文

我正在尝试制作一个尽可能真实的球物理模拟器。目前,我已经计算出了球之间的所有碰撞,当球很少时效果非常好。但一旦你创建了大约 400-500 个球,事情就开始变得奇怪:底部的球被压扁,彼此重叠。一旦你添加更多的球,事情就会变得更加疯狂,球几乎开始在底部传送。

一些截图: 100 个球: 球堆正常启动,无故障拥有超过 600 个球:

有谁知道发生了什么以及造成这种情况的原因是什么?如何改进代码来防止这种情况发生?

(为了便于测试,使用滚轮快速连续创建大量球)

这是代码,它不太长:

import pygame
from pygame.locals import *
from random import *
from math import *

WIDTH = 1200
HEIGHT = 800
FPS = 144
VEC = pygame.math.Vector2

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT), HWSURFACE | DOUBLEBUF)
pygame.display.set_caption("Bouncy balls with physics")
clock = pygame.time.Clock()

gravity = 3200
colors = range(50, 255, 10)
sizes = (15, 25)

absvec = lambda v: VEC(abs(v.x), abs(v.y))
inttup = lambda tup: tuple((int(tup[0]), int(tup[1])))

class Ball:
    instances = []
    regions = {}

    def __init__(self, pos):
        __class__.instances.append(self)
        self.pos = VEC(pos)
        self.region = inttup(self.pos // (sizes[1] * 2) + VEC(1, 1))
        if self.region in __class__.regions:
            __class__.regions[self.region].append(self)
        else:
            __class__.regions[self.region] = [self]
        self.vel = VEC(0, 0)
        self.radius = randint(*sizes)
        self.mass = self.radius ** 2 * pi
        self.color = (choice(colors), choice(colors), choice(colors))
        self.moving = True

    def update_position(self):
        self.vel.y += gravity * dt
        self.vel -= self.vel.normalize() * 160 * dt
        if -6 < self.vel.x < 6:
            self.vel.x = 0
        if -6 < self.vel.y < 6:
            self.vel.y = 0
        self.pos += self.vel * dt

        new_region = inttup(self.pos // (sizes[1] * 2) + VEC(1, 1))
        if self.region != new_region:
            if new_region in __class__.regions:
                __class__.regions[new_region].append(self)
            else:
                __class__.regions[new_region] = [self]
            __class__.regions[self.region].remove(self)
            self.region = new_region

    def update_pushout(self):
        self.collisions = []
        for x in range(self.region[0] - 1, self.region[0] + 2):
            for y in range(self.region[1] - 1, self.region[1] + 2):
                if (x, y) in __class__.regions:
                    for ball in __class__.regions[(x, y)]:
                        dist = self.pos.distance_to(ball.pos)
                        if dist < self.radius + ball.radius and ball != self:
                            self.collisions.append(ball)
                            overlap = -(dist - self.radius - ball.radius) * 0.5
                            self.pos += overlap * (self.pos - ball.pos).normalize()
                            ball.pos -= overlap * (self.pos - ball.pos).normalize()
    
    def update_collision(self):
        for ball in self.collisions:
            self.vel *= 0.85
            n = (ball.pos - self.pos).normalize()
            k = self.vel - ball.vel
            p = 2 * (n * k) / (self.mass + ball.mass)
            self.vel -= p * ball.mass * n
            ball.vel += p * self.mass * n

        if self.pos.x < self.radius:
            self.vel.x *= -0.8
            self.pos.x = self.radius
        elif self.pos.x > WIDTH - self.radius:
            self.vel.x *= -0.8
            self.pos.x = WIDTH - self.radius
        if self.pos.y < self.radius:
            self.vel.y *= -0.8
            self.pos.y = self.radius
        elif self.pos.y > HEIGHT - self.radius:
            if self.vel.y <= gravity * dt:
                self.vel.y = 0
            else:
                self.vel.y *= -0.8
            self.pos.y = HEIGHT - self.radius

    def draw(self, screen):
        pygame.draw.circle(screen, self.color, self.pos, self.radius)

    def kill(self):
        __class__.instances.remove(self)
        __class__.regions[self.region].remove(self)
        del self

running = True
while running:
    dt = clock.tick_busy_loop(FPS) / 1000
    screen.fill((30, 30, 30))
    pygame.display.set_caption(f"Bouncy balls with physics | FPS: {str(int(clock.get_fps()))} | Ball count: {len(Ball.instances)}")

    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == MOUSEBUTTONDOWN:
            mpos = VEC(pygame.mouse.get_pos())
            if sum([len(balls) for balls in Ball.regions.values()]) <= 1000:
                Ball(mpos)
        if event.type == KEYDOWN:
            if event.key == K_c:
                for ball in Ball.instances.copy():
                    ball.kill()

    for ball in Ball.instances:
        ball.update_position()
        ball.update_pushout()
        ball.update_collision()
        ball.draw(screen)

    pygame.display.flip()

pygame.quit()
quit()

这是两个球碰撞时执行的代码:

self.vel *= 0.85
n = (ball.pos - self.pos).normalize()
k = self.vel - ball.vel
p = 2 * (n * k) / (self.mass + ball.mass)
self.vel -= p * ball.mass * n
ball.vel += p * self.mass * n

从此维基百科页面获取:https://en.wikipedia.org/wiki/Elastic_collision

提前致谢!

I'm trying to make an as realistic as possible ball physics simulator. Currently, I worked out all the collisions between balls, and it works really well when there are few balls. But once you create around and over 400-500 balls, things start to get weird: The balls at the bottom get squished around, overlapping with one another. And once you add even more balls, things get even crazier, the balls start to almost teleport around at the bottom.

Some screenshots:
With 100 balls:
Balls piling up normally without glitching
With over 600 balls:
Balls teleporting and overlapping at the bottom

Does anyone know what is happening and what is causing this? How can the code be improved to prevent it?

(For ease of testing, use scroll wheel to create lots of balls in quick succession)

Here is the code, it isn't too long:

import pygame
from pygame.locals import *
from random import *
from math import *

WIDTH = 1200
HEIGHT = 800
FPS = 144
VEC = pygame.math.Vector2

pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT), HWSURFACE | DOUBLEBUF)
pygame.display.set_caption("Bouncy balls with physics")
clock = pygame.time.Clock()

gravity = 3200
colors = range(50, 255, 10)
sizes = (15, 25)

absvec = lambda v: VEC(abs(v.x), abs(v.y))
inttup = lambda tup: tuple((int(tup[0]), int(tup[1])))

class Ball:
    instances = []
    regions = {}

    def __init__(self, pos):
        __class__.instances.append(self)
        self.pos = VEC(pos)
        self.region = inttup(self.pos // (sizes[1] * 2) + VEC(1, 1))
        if self.region in __class__.regions:
            __class__.regions[self.region].append(self)
        else:
            __class__.regions[self.region] = [self]
        self.vel = VEC(0, 0)
        self.radius = randint(*sizes)
        self.mass = self.radius ** 2 * pi
        self.color = (choice(colors), choice(colors), choice(colors))
        self.moving = True

    def update_position(self):
        self.vel.y += gravity * dt
        self.vel -= self.vel.normalize() * 160 * dt
        if -6 < self.vel.x < 6:
            self.vel.x = 0
        if -6 < self.vel.y < 6:
            self.vel.y = 0
        self.pos += self.vel * dt

        new_region = inttup(self.pos // (sizes[1] * 2) + VEC(1, 1))
        if self.region != new_region:
            if new_region in __class__.regions:
                __class__.regions[new_region].append(self)
            else:
                __class__.regions[new_region] = [self]
            __class__.regions[self.region].remove(self)
            self.region = new_region

    def update_pushout(self):
        self.collisions = []
        for x in range(self.region[0] - 1, self.region[0] + 2):
            for y in range(self.region[1] - 1, self.region[1] + 2):
                if (x, y) in __class__.regions:
                    for ball in __class__.regions[(x, y)]:
                        dist = self.pos.distance_to(ball.pos)
                        if dist < self.radius + ball.radius and ball != self:
                            self.collisions.append(ball)
                            overlap = -(dist - self.radius - ball.radius) * 0.5
                            self.pos += overlap * (self.pos - ball.pos).normalize()
                            ball.pos -= overlap * (self.pos - ball.pos).normalize()
    
    def update_collision(self):
        for ball in self.collisions:
            self.vel *= 0.85
            n = (ball.pos - self.pos).normalize()
            k = self.vel - ball.vel
            p = 2 * (n * k) / (self.mass + ball.mass)
            self.vel -= p * ball.mass * n
            ball.vel += p * self.mass * n

        if self.pos.x < self.radius:
            self.vel.x *= -0.8
            self.pos.x = self.radius
        elif self.pos.x > WIDTH - self.radius:
            self.vel.x *= -0.8
            self.pos.x = WIDTH - self.radius
        if self.pos.y < self.radius:
            self.vel.y *= -0.8
            self.pos.y = self.radius
        elif self.pos.y > HEIGHT - self.radius:
            if self.vel.y <= gravity * dt:
                self.vel.y = 0
            else:
                self.vel.y *= -0.8
            self.pos.y = HEIGHT - self.radius

    def draw(self, screen):
        pygame.draw.circle(screen, self.color, self.pos, self.radius)

    def kill(self):
        __class__.instances.remove(self)
        __class__.regions[self.region].remove(self)
        del self

running = True
while running:
    dt = clock.tick_busy_loop(FPS) / 1000
    screen.fill((30, 30, 30))
    pygame.display.set_caption(f"Bouncy balls with physics | FPS: {str(int(clock.get_fps()))} | Ball count: {len(Ball.instances)}")

    for event in pygame.event.get():
        if event.type == QUIT:
            running = False
        if event.type == MOUSEBUTTONDOWN:
            mpos = VEC(pygame.mouse.get_pos())
            if sum([len(balls) for balls in Ball.regions.values()]) <= 1000:
                Ball(mpos)
        if event.type == KEYDOWN:
            if event.key == K_c:
                for ball in Ball.instances.copy():
                    ball.kill()

    for ball in Ball.instances:
        ball.update_position()
        ball.update_pushout()
        ball.update_collision()
        ball.draw(screen)

    pygame.display.flip()

pygame.quit()
quit()

This is the code that gets performed when two balls collide:

self.vel *= 0.85
n = (ball.pos - self.pos).normalize()
k = self.vel - ball.vel
p = 2 * (n * k) / (self.mass + ball.mass)
self.vel -= p * ball.mass * n
ball.vel += p * self.mass * n

gotten from this wikipedia page: https://en.wikipedia.org/wiki/Elastic_collision

Thanks in advance!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

三五鸿雁 2025-01-23 19:32:09

这是由于您添加的重力因子,这会导致圆环“下降”并在它们应该处于平衡时挤压它们下方的圆环

我现在无法测试它,但我的猜测是将 g 应用于您的 k 因子而不是主速度(我也不是100%确定)

告诉我是否是这样

it is due to the gravity factor you added, this causes cercles to "go down" and squish the cercles under them when they should be in equilibrium

I can't test it right now but my guess would be to apply g to your k factor instead of the main velocity (I'm not 100% sure tho)

Tell me if it does

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文