首页 > 解决方案 > 如何让两个粒子在 tkinter python 3 中碰撞?

问题描述

我目前正在尝试使两个处于气态的粒子发生碰撞,但我不太确定如何编写代码来实现这一点,我有一个粗略的草图,但我真的不知道碰撞发生后,我如何正确实现它(第 43-54 行,即注释 #collision of i 与另一个粒子 j 之后的代码)我希望它们以不同的速度朝相反的方向前进,因为我将计算动能取决于关于粒子的质量。该项目的目标是基本显示在气体中移动的多个不同参数(速度、质量、方向)的粒子的动能守恒。这是我当前的代码,任何帮助将不胜感激!!:

from tkinter import *
from random import *
myHeight=400
myWidth=600
mySpeed=10

x1=5
y=5
radius1=20

x2=7
y2=4
radius1=20

x=60
width=40
length=10

global particules 
particules = []

def initialiseBall(dx,dy,radius,color):
    b = [myWidth/2,myHeight/2, dx, dy, radius]
    particules.append(b)
    k = myCanvas.create_oval(myWidth/2-radius, myHeight/2,\
                        myWidth/2+radius,myHeight/2+radius,\
                        width=2,fill=color)
    print(k)

def updateBalls():
  N = len(particules)

  for i in range(N):
    particules[i][0] += particules [i][2]
    particules[i][1] += particules [i][3]

    # collision of i with the walls
    if particules[i][0]<0 or particules[i][0]>=myWidth:
          particules[i][2] *= -1
    if particules[i][1]<0 or particules[i][1]>=myHeight:
          particules[i][3] *= -1

    #collision of i with another particle j
    # for j in range(N):
    #   if j != i:
        # compare the position of i and j
        # dij = ...
        # if dij ... :
          #if collision, compute the normal vector
          #change velocities

  #  if particules[i][1]<=particules[i][1]:
  #    particules[i][2] *= -1
  #  r = particules[i][4]

    myCanvas.coords(i+1, particules[i][0]-particules[i][4],
    particules[i][1]-particules[i][4], 
    particules[i][0]+particules[i][4], 
    particules[i][1]+particules[i][4])

def animation ():
    updateBalls()
    myCanvas.after(mySpeed, animation)

def kineticenergy(mass, velocity):
  Ec = 1/2 * mass * velocity ** 2
  return Ec

# def averagetrip(number, radius):
#   # 
#   #
#   # 
#   #

mainWindow=Tk()
mainWindow.title('particles reservoir')

myCanvas = Canvas(mainWindow, bg = 'grey', height = myHeight, width = myWidth)
myCanvas.pack(side=TOP)

# create 2 particules 
initialiseBall(-1,0, 50, 'red')
initialiseBall(1,0, 50, 'blue')

print(particules)

'''
N = 100
for n in range(N):
  initialiseBalle(-1 ,0, randint(5,10), 'red')
'''

animation()
mainWindow.mainloop()

标签: pythonpython-3.xtkinter

解决方案


尝试这个:

from math import sqrt, sin, cos
import tkinter as tk
import time

# For more info about this read: https://stackoverflow.com/a/17985217/11106801
def _create_circle(self, x, y, r, **kwargs):
    return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
tk.Canvas.create_circle = _create_circle


WINDOW_WIDTH = 500
WINDOW_HEIGHT = 500

# The coefficient of restitution
# Set to 1 for perfectly elastic collitions
e = 1


class Ball:
    def __init__(self, mass:float, r:float, x:float, y:float,
                 vx:float=0, vy:float=0, **kwargs):
        """
        This is a class that defines what a ball is and how it interacts
        with other balls.

        Arguments:
            mass:float    # The mass of the ball
                          ------------------------------------------------------
            r:float       # The radius of the ball, must be >0
                          ------------------------------------------------------
            x:float       # The x position of the ball
                          #   must be >0 and <WINDOW_WIDTH
            y:float       # The y position of the ball
                          #   must be >0 and <WINDOW_HEIGHT
                          ------------------------------------------------------
            vx:float      # The x velocity of the ball
            vy:float      # The y velocity of the ball
                          ------------------------------------------------------
            **kwargs      # All of the args to be passed in to `create_circle`
        """
        self.m = mass
        self.r = r
        self.x = x
        self.y = y
        self.vx = vx
        self.vy = vy
        self.kwargs = kwargs

    def display(self, canvas:tk.Canvas) -> int:
        """
        Displays the ball on the screen and returns the canvas_id (which
        is a normal python int).
        """
        canvas_id = canvas.create_circle(self.x, self.y, self.r, **self.kwargs)
        return canvas_id

    def move(self, all_balls:list, dt:float) -> (float, float):
        """
        This moves the ball according to `self.vx` and `self.vy`.
        It also checks for collisions with other balls.

        Arguments:
            all_balls:list   # A list of all balls that are in the
                             # simulation. It can include this ball.
                             ---------------------------------------------------
            dt:float         # delta time - used to move the balls

        Returns:
            dx:float         # How much the ball has moved in the x direction
            dy:float         # How much the ball has moved in the y direction

        Note: This function isn't optimised in any way. If you optimise
        it, it should run faster.
        """

        # Check if there are any collitions:
        for other, _ in all_balls:
            # Skip is `ball` is the same as this ball
            if id(other) == id(self):
                continue
            # Check if there is a collision:
            distance_squared = (other.x - self.x)**2 + (other.y - self.y)**2
            if distance_squared <= (other.r + self.r)**2:
                # Now the fun part - calulating the resultant velocity.
                # I am assuming you already know all of the reasoning
                # behind the math (if you don't ask physics.stackexchange.com)

                # First I will find the normal vector of the balls' radii.
                # That is just the unit vector in the direction of the
                #  balls' radii
                ball_radii_vector_x = other.x - self.x
                ball_radii_vector_y = other.y - self.y
                abs_ball_radii_vector = sqrt(ball_radii_vector_x**2 +\
                                             ball_radii_vector_y**2)
                nx = ball_radii_vector_x / abs_ball_radii_vector        **2*2
                ny = ball_radii_vector_y / abs_ball_radii_vector        **2*2

                # Now I will calculate the tangent
                tx = -ny
                ty = nx

                """ Only for debug
                print("n =", (nx, ny), "\t t =", (tx, ty))
                print("u1 =", (self.vx, self.vy),
                      "\t u2 =", (other.vx, other.vy))
                #"""

                # Now I will split the balls' velocity vectors to the sum
                #  of 2 vectors parallel to n and t
                # self_velocity  = λ*n + μ*t
                # other_velocity = a*n + b*t
                λ = (self.vx*ty - self.vy*tx) / (nx*ty - ny*tx)
                # Sometimes `tx` can be 0 if so we are going to use `ty` instead
                try:
                    μ = (self.vx - λ*nx) / tx
                except ZeroDivisionError:
                    μ = (self.vy - λ*ny) / ty

                """ Only for debug
                print("λ =", λ, "\t μ =", μ)
                #"""

                a = (other.vx*ty - other.vy*tx) / (nx*ty - ny*tx)
                # Sometimes `tx` can be 0 if so we are going to use `ty` instead
                try:
                    b = (other.vx - a*nx) / tx
                except ZeroDivisionError:
                    b = (other.vy - a*ny) / ty

                """ Only for debug
                print("a =", a, "\t b =", b)
                #"""

                self_u = λ
                other_u = a

                sum_mass_u = self.m*self_u + other.m*other_u
                sum_masses = self.m + other.m

                # Taken from en.wikipedia.org/wiki/Inelastic_collision
                self_v = (e*other.m*(other_u-self_u) + sum_mass_u)/sum_masses
                other_v = (e*self.m*(self_u-other_u) + sum_mass_u)/sum_masses

                self.vx = self_v*nx + μ*tx
                self.vy = self_v*ny + μ*ty
                other.vx = other_v*nx + b*tx
                other.vy = other_v*ny + b*ty

                print("v1 =", (self.vx, self.vy),
                      "\t v2 =", (other.vx, other.vy))

        # Move the ball
        dx = self.vx * dt
        dy = self.vy * dt
        self.x += dx
        self.y += dy
        return dx, dy


class Simulator:
    def __init__(self):
        self.balls = [] # Contains tuples of (<Ball>, <canvas_id>)
        self.root = tk.Tk()
        self.root.resizable(False, False)
        self.canvas = tk.Canvas(self.root, width=WINDOW_WIDTH,
                                height=WINDOW_HEIGHT)
        self.canvas.pack()

    def step(self, dt:float) -> None:
        """
        Steps the simulation as id `dt` seconds have passed
        """
        for ball, canvas_id in self.balls:
            dx, dy = ball.move(self.balls, dt)
            self.canvas.move(canvas_id, dx, dy)

    def run(self, dt:float, total_time:float) -> None:
        """
        This function keeps steping `dt` seconds until `total_time` has
        elapsed.

        Arguments:
            dt:float          # The time resolution in seconds
            total_time:float  # The number of seconds to simulate

        Note: This function is just for proof of concept. It is badly written.
        """
        for i in range(int(total_time//dt)):
            self.step(dt)
            self.root.update()
            time.sleep(dt)

    def add_ball(self, *args, **kwargs) -> None:
        """
        Adds a ball by passing all of the args and keyword args to `Ball`.
        It also displays the ball and appends it to the list of balls.
        """
        ball = Ball(*args, **kwargs)
        canvas_id = ball.display(self.canvas)
        self.balls.append((ball, canvas_id))


app = Simulator()
app.add_ball(mass=1, r=10, x=20, y=250, vx=100, vy=0, fill="red")
app.add_ball(mass=1, r=10, x=240, y=250, vx=0, vy=0, fill="blue")
app.add_ball(mass=1, r=10, x=480, y=250, vx=0, vy=0, fill="yellow")
app.run(0.01, 10)

该代码有很多注释描述它是如何工作的。如果你还有什么问题,可以问我。该move方法也没有优化。该run方法不是很好,如果它仍在运行并且用户关闭窗口,则可能会引发错误。我将尝试为该方法找到更好的方法。如果您发现任何错误,请告诉我。我会尝试修复它们。


推荐阅读