Advertisement
here2share

# Tk_elastic_ball_collisions.py

Mar 18th, 2022
1,249
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.59 KB | None | 0 0
  1. # Tk_elastic_ball_collisions.py
  2. # to fix: will eventually gets wonky overlapped despite the ghosting feature
  3.  
  4. from math import sqrt, sin, cos
  5. import tkinter as tk
  6. import time
  7.  
  8. def _create_circle(self, x, y, r, **kwargs):
  9.     return self.create_oval(x-r, y-r, x+r, y+r, **kwargs)
  10. tk.Canvas.create_circle = _create_circle
  11.  
  12.  
  13. WINDOW_WIDTH = 200
  14. WINDOW_HEIGHT = 200
  15.  
  16. # The coefficient of restitution
  17. # Set to 1 for perfectly elastic collisions
  18. e = 1
  19.  
  20.  
  21. class Ball:
  22.     def __init__(self, mass:float, r:float, x:float, y:float,
  23.                  vx:float=0, vy:float=0, ghost:int=0, **kwargs):
  24.         """
  25.         This is a class that defines what a ball is and how it interacts
  26.         with other balls.
  27.  
  28.         Arguments:
  29.             mass:float    # The mass of the ball
  30.                           ------------------------------------------------------
  31.             r:float       # The radius of the ball, must be >0
  32.                           ------------------------------------------------------
  33.             x:float       # The x position of the ball
  34.                           #   must be >0 and <WINDOW_WIDTH
  35.             y:float       # The y position of the ball
  36.                           #   must be >0 and <WINDOW_HEIGHT
  37.                           ------------------------------------------------------
  38.             vx:float      # The x velocity of the ball
  39.             vy:float      # The y velocity of the ball
  40.                           ------------------------------------------------------
  41.             **kwargs      # All of the args to be passed in to `create_circle`
  42.         """
  43.         self.m = mass
  44.         self.r = r
  45.         self.x = x
  46.         self.y = y
  47.         self.vx = vx
  48.         self.vy = vy
  49.         self.ghost = ghost
  50.         self.kwargs = kwargs
  51.  
  52.     def display(self, canvas:tk.Canvas) -> int:
  53.         """
  54.         Displays the ball on the screen and returns the canvas_id (which
  55.         is a normal python int).
  56.         """
  57.         canvas_id = canvas.create_circle(self.x, self.y, self.r, **self.kwargs)
  58.         return canvas_id
  59.  
  60.     def move(self, all_balls:list, dt:float) -> (float, float):
  61.         """
  62.         This moves the ball according to `self.vx` and `self.vy`.
  63.         It also checks for collisions with other balls.
  64.  
  65.         Arguments:
  66.             all_balls:list   # A list of all balls that are in the
  67.                              # simulation. It can include this ball.
  68.                              ---------------------------------------------------
  69.             dt:float         # delta time - used to move the balls
  70.  
  71.         Returns:
  72.             dx:float         # How much the ball has moved in the x direction
  73.             dy:float         # How much the ball has moved in the y direction
  74.  
  75.         Note: This function isn't optimised in any way. If you optimise
  76.         it, it should run faster.
  77.         """
  78.  
  79.         # Check if there are any wall collisions:
  80.         if self.x - self.r < 0:
  81.             self.vx = self.vx * -1
  82.             self.x += 0.01
  83.         elif self.x + self.r > WINDOW_WIDTH:
  84.             self.vx = self.vx * -1
  85.             self.x -= 0.01
  86.         if self.y - self.r < 0:
  87.             self.vy = self.vy * -1
  88.             self.y += 0.01
  89.         elif self.y + self.r > WINDOW_HEIGHT:
  90.             self.vy = self.vy * -1
  91.             self.y -= 0.01
  92.  
  93.         # Check if there are any ball collisions:
  94.         for other, _ in all_balls:
  95.             # Skip is `ball` is the same as this ball
  96.             if id(other) == id(self):
  97.                 continue
  98.             # Check if there is a collision:
  99.             distance_squared = (other.x - self.x)**2 + (other.y - self.y)**2
  100.             if distance_squared <= (other.r + self.r)**2:
  101.                 # Now the fun part - calulating the resultant velocity.
  102.                 if not self.ghost:
  103.                     self.ghost = 5
  104.  
  105.                     # First I will find the normal vector of the balls' radii.
  106.                     # That is just the unit vector in the direction of the
  107.                     #  balls' radii
  108.                     ball_radii_vector_x = other.x - self.x
  109.                     ball_radii_vector_y = other.y - self.y
  110.                     abs_ball_radii_vector = sqrt(ball_radii_vector_x**2 +\
  111.                                                  ball_radii_vector_y**2)
  112.                     nx = ball_radii_vector_x / abs_ball_radii_vector        **2*2
  113.                     ny = ball_radii_vector_y / abs_ball_radii_vector        **2*2
  114.  
  115.                     # Now I will calculate the tangent
  116.                     tx = -ny
  117.                     ty = nx
  118.  
  119.                     """ Only for debug
  120.                     print("n =", (nx, ny), "\t t =", (tx, ty))
  121.                     print("u1 =", (self.vx, self.vy),
  122.                           "\t u2 =", (other.vx, other.vy))
  123.                     #"""
  124.  
  125.                     # Now I will split the balls' velocity vectors to the sum
  126.                     #  of 2 vectors parallel to n and t
  127.                     # self_velocity  = λ*n + μ*t
  128.                     # other_velocity = a*n + b*t
  129.                     λ = (self.vx*ty - self.vy*tx) / (nx*ty - ny*tx)
  130.                     # Sometimes `tx` can be 0 if so we are going to use `ty` instead
  131.                     try:
  132.                         μ = (self.vx - λ*nx) / tx
  133.                     except ZeroDivisionError:
  134.                         μ = (self.vy - λ*ny) / ty
  135.  
  136.                     """ Only for debug
  137.                     print("λ =", λ, "\t μ =", μ)
  138.                     #"""
  139.  
  140.                     a = (other.vx*ty - other.vy*tx) / (nx*ty - ny*tx)
  141.                     # Sometimes `tx` can be 0 if so we are going to use `ty` instead
  142.                     try:
  143.                         b = (other.vx - a*nx) / tx
  144.                     except ZeroDivisionError:
  145.                         b = (other.vy - a*ny) / ty
  146.  
  147.                     """ Only for debug
  148.                     print("a =", a, "\t b =", b)
  149.                     #"""
  150.  
  151.                     self_u = λ
  152.                     other_u = a
  153.  
  154.                     sum_mass_u = self.m*self_u + other.m*other_u
  155.                     sum_masses = self.m + other.m
  156.  
  157.                     # Inelastic_collision
  158.                     self_v = (e*other.m*(other_u-self_u) + sum_mass_u)/sum_masses
  159.                     other_v = (e*self.m*(self_u-other_u) + sum_mass_u)/sum_masses
  160.  
  161.                     self.vx = self_v*nx + μ*tx
  162.                     self.vy = self_v*ny + μ*ty
  163.                     other.vx = other_v*nx + b*tx
  164.                     other.vy = other_v*ny + b*ty
  165.  
  166.                     print("v1 =", (self.vx, self.vy),
  167.                           "\t v2 =", (other.vx, other.vy))
  168.                      
  169.             else:
  170.                 self.ghost = max(0, self.ghost - 1)
  171.  
  172.         # Move the ball
  173.         dx = self.vx * dt
  174.         dy = self.vy * dt
  175.         self.x += dx
  176.         self.y += dy
  177.         return dx, dy
  178.  
  179.  
  180. class Simulator:
  181.     def __init__(self):
  182.         self.balls = [] # Contains tuples of (<Ball>, <canvas_id>)
  183.         self.root = tk.Tk()
  184.         self.root.resizable(False, False)
  185.         self.canvas = tk.Canvas(self.root, width=WINDOW_WIDTH,
  186.                                 height=WINDOW_HEIGHT)
  187.         self.canvas.pack()
  188.  
  189.     def step(self, dt:float) -> None:
  190.         """
  191.         Steps the simulation as id `dt` seconds have passed
  192.         """
  193.         for ball, canvas_id in self.balls:
  194.             dx, dy = ball.move(self.balls, dt)
  195.             self.canvas.move(canvas_id, dx, dy)
  196.  
  197.     def run(self, dt:float, total_time:float) -> None:
  198.         """
  199.         Note: This function is just for proof of concept which disregards
  200.         the slowing simulation as a whole.
  201.         """
  202.         while 1:
  203.             self.step(dt)
  204.             self.root.update()
  205.             time.sleep(dt)
  206.  
  207.     def add_ball(self, *args, **kwargs) -> None:
  208.         """
  209.         Adds a ball by passing all of the args and keyword args to `Ball`.
  210.         It also displays the ball and appends it to the list of balls.
  211.         """
  212.         ball = Ball(*args, **kwargs)
  213.         canvas_id = ball.display(self.canvas)
  214.         self.balls.append((ball, canvas_id))
  215.  
  216.  
  217. app = Simulator()
  218. app.add_ball(mass=1, r=20, x=20, y=100, vx=240, vy=0, fill="red")
  219. app.add_ball(mass=1, r=20, x=100, y=100, vx=0, vy=0, fill="blue")
  220. app.add_ball(mass=1, r=20, x=180, y=98, vx=0, vy=0, fill="yellow")
  221. app.run(0.01, 10000)
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement