粒子相互吸引不正常

3
我正在尝试在Python中使粒子相互吸引。它有些效果,但它们总是移动到左上角(0;0)。
一年前,CodeParade发布了一个关于他用粒子制作的生命游戏的视频。我觉得很酷,想在Python中重新创建它。这并不难,但我有一个问题。每当一些粒子足够接近以相互吸引时,它们会变得更加接近,但同时它们会“奔跑”到左上角,这恰好是(0;0)。起初我认为我没有正确应用吸引力效应,但反复阅读后我没有发现任何错误。是否有人知道为什么它不能按预期工作?
/ 这是代码 /
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pygame, random, time
import numpy as np

attraction = [  [-2.6,8.8,10.2,0.7],
                [4.1,-3.3,-3.1,4.4],
                [0.6,3.7,-0.4,5.1],
                [-7.8,0.3,0.3,0.0]]

minR = [[100.0,100.0,100.0,100.0],
        [100.0,100.0,100.0,100.0],
        [100.0,100.0,100.0,100.0],
        [100.0,100.0,100.0,100.0]]

maxR = [[41.7,16.4,22.1,15.0],
        [16.4,41.7,32.0,75.1],
        [22.1,32.0,55.7,69.9],
        [15.0,75.1,69.9,39.5]]

colors = [  (200,50,50),
            (200,100,200),
            (100,255,100),
            (50,100,100)]

#Rouge
#Violet
#Vert
#Cyan

particles = []

#Number of particles
numberParticles = 5

#Width
w = 500

#Height
h = 500

#Radius of particles
r = 4

#Rendering speed
speed = 0.05

#Attraction speed factor
speedFactor = 0.01

#Min distance factor
minRFactor = 0.1

#Max distance factor
maxRFactor = 2

#Attraction factor
attractionFactor = 0.01

def distance(ax, ay, bx, by):
    return intg((ax - bx)**2 + (ay - by)**2)

def intg(x):
    return int(round(x))

def display(plan):
    #Fill with black
    #Momentarily moved to main
    #pygame.Surface.fill(plan,(0,0,0))

    #For each particle, draw it
    for particle in particles:
        pygame.draw.circle(plan,colors[particle[0]],(particle[1],particle[2]),r)

    #Update display
    pygame.display.flip()

def update(particles):
    newParticles = []

    for particleIndex in xrange(len(particles)):
        typeId, x, y = particles[particleIndex]

        othersX = [[],[],[],[]]
        othersY = [[],[],[],[]]

        #For every other particles
        for otherParticle in particles[0:particleIndex]+particles[particleIndex+1:]:

            otherTypeId, otherX, otherY = otherParticle


            """
            #Draw minR and maxR of attraction for each color
            pygame.draw.circle(screen,colors[otherTypeId],(x,y),intg(minR[typeId][otherTypeId] * minRFactor),1)
            pygame.draw.circle(screen,colors[otherTypeId],(x,y),intg(maxR[typeId][otherTypeId] * maxRFactor),1)
            """

            #If otherParticle is between minR and maxR from (x;y)
            if (minR[typeId][otherTypeId] * minRFactor)**2 <= distance(x,y,otherX,otherY) <= (maxR[typeId][otherTypeId] * maxRFactor)**2:

                #Append otherParticle's coordinates to othersX and othersY respectively
                othersX[otherTypeId].append(otherX)
                othersY[otherTypeId].append(otherY)

        #Take the average attractions for each color
        othersX = [np.mean(othersX[i]) * attraction[typeId][i] * attractionFactor for i in xrange(len(othersX)) if othersX[i] != []]
        othersY = [np.mean(othersY[i]) * attraction[typeId][i] * attractionFactor for i in xrange(len(othersY)) if othersY[i] != []]

        #If not attracted, stay in place
        if othersX == []:
            newX = x

        else:

            #Take the average attraction
            avgX = np.mean(othersX)

            #Determine the new x position
            newX = x - (x - avgX) * speedFactor

            #If out of screen, warp
            if newX > w:
                newX -= w

            elif newX < 0:
                newX += w

        #If not attracted, stay in place
        if othersY == []:
            newY = y

        else:

            #Take the average attraction
            avgY = np.mean(othersY)

            #Determine the new y position
            newY = y - (y - avgY) * speedFactor

            #If out of screen, warp
            if newY > h:
                newY -= h

            elif newY < 0:
                newY += h

        #Append updated particle to newParticles
        newParticles.append([typeId,intg(newX),intg(newY)])

    return newParticles

if __name__ == "__main__":

    #Initialize pygame screen
    pygame.init()
    screen = pygame.display.set_mode([w,h])

    #Particle = [type,posX,posY]
    #Create randomly placed particles of random type
    for x in xrange(numberParticles):
        particles.append([random.randint(0,3),random.randint(0,w),random.randint(0,h)])

    display(screen)

    #Wait a bit
    time.sleep(1)

    while True:
        #raw_input()
        #Fill the screen with black
        pygame.Surface.fill(screen,(0,0,0))

        #Update particles
        particles = update(particles)

        #Display particles
        display(screen)

        #Wait a bit
        time.sleep(speed)

2个回答

3
问题出现在以下代码行中:
othersX = [np.mean(othersX[i]) * attraction[typeId][i] * attractionFactor for i in range(len(othersX)) if othersX[i] != []]
othersY = [np.mean(othersY[i]) * attraction[typeId][i] * attractionFactor for i in range(len(othersY)) if othersY[i] != []]

othersXothersY应该是坐标位置,但由于坐标乘以attraction[typeId][i] * attractionFactor,导致坐标向左上角偏移。

可以通过忽略这些因素来轻松地进行评估:

othersX = [np.mean(othersX[i]) for i in range(len(othersX)) if othersX[i] != []]
othersY = [np.mean(othersY[i]) for i in range(len(othersY)) if othersY[i] != []]

一种选择是使用向量形式(xy)到(otherXotherY),而不是位置:
for otherParticle in particles[0:particleIndex]+particles[particleIndex+1:]:
    otherTypeId, otherX, otherY = otherParticle

    if (minR[typeId][otherTypeId] * minRFactor)**2 <= distance(x,y,otherX,otherY) <= (maxR[typeId][otherTypeId] * maxRFactor)**2:

        # Append otherParticle's coordinates to othersX and othersY respectively
        othersX[otherTypeId].append(otherX - x)
        othersY[otherTypeId].append(otherY - y)

othersX = [np.mean(othersX[i]) * attraction[typeId][i] * attractionFactor for i in range(len(othersX)) if othersX[i] != []]
othersY = [np.mean(othersY[i]) * attraction[typeId][i] * attractionFactor for i in range(len(othersY)) if othersY[i] != []]

当然,你也需要调整新位置的计算:

avgX = np.mean(othersX)
newX = x + avgX * speedFactor

avgY = np.mean(othersY)
newY = y + avgY * speedFactor

如其他回答中所述,您应该使用浮点数进行计算:
def distance(ax, ay, bx, by):
    # return intg((ax - bx)**2 + (ay - by)**2)
    return (ax - bx)**2 + (ay - by)**2

# newParticles.append([typeId,intg(newX),intg(newY)])
newParticles.append([typeId, newX, newY])

绘制圆形时,将其圆心的坐标四舍五入为整数坐标:

for particle in particles:
    # pygame.draw.circle(plan,colors[particle[0]],(particle[1],particle[2]),r)
    pygame.draw.circle(plan,colors[particle[0]],(intg(particle[1]),intg(particle[2])),r)

但是我不能将attraction[typeId][i]avgXavgY相乘,因为它取决于粒子的类型。例如,绿色粒子会比紫色粒子更吸引红色粒子。 - Lord Baryhobal
哦,我想我明白了。我会尝试一些东西并给你更新。 - Lord Baryhobal
@LordBaryhobal,我已经在答案中添加了一个可能的解决方案。 - Rabbid76

0
可能是这一行:
newParticles.append([typeId,intg(newX),intg(newY)])

你之前使用高精度计算了粒子的位置,但是在保存到newparticles之前,intg()会将所有数字向0舍入。随着时间的推移,这将导致偏向于[0,0]

我会通过保持particlesnewparticles中的数据为浮点精度,只有在必须在屏幕上显示时才进行舍入来解决这个问题。这样,您使用的高精度将从一个时间步长保持到下一个时间步长。


1
这也是我的第一个猜测。但我尝试了一下,发现这不是问题所在。 - Rabbid76

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接