cookertron

1000 Ball Collision Using Spatial Hash

May 28th, 2020
271
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 3.08 KB | None | 0 0
  1. # 1000 balls find an equilibrium spatial hash collision
  2. # credit to Daniel Pope for showing me the light :)
  3. # fb.com/groups/pygame
  4.  
  5. import pygame
  6. import random, math
  7. from itertools import product
  8.  
  9. SPATIAL_GRID_SIZE = 32
  10.  
  11. class SpatialHash:
  12.     def __init__(s):
  13.         s.grid = [{}, {}]
  14.         s.currentGrid = 0
  15.  
  16.     def switchGrid(s):
  17.         s.currentGrid = 1 - s.currentGrid
  18.         s.grid[s.currentGrid] = {}
  19.  
  20.     def insert(s, entity):
  21.         for cell in s._rect_cells(entity.rect()):
  22.             items = s.grid[s.currentGrid].get(cell)
  23.             if items is None:
  24.                 s.grid[s.currentGrid][cell] = [entity]
  25.             else:
  26.                 items.append(entity)
  27.  
  28.     def _rect_cells(s, rect):
  29.         x1, y1 = rect.topleft
  30.         x1 //= SPATIAL_GRID_SIZE
  31.         y1 //= SPATIAL_GRID_SIZE
  32.         x2, y2 = rect.bottomright
  33.         x2 = x2 // SPATIAL_GRID_SIZE + 1
  34.         y2 = y2 // SPATIAL_GRID_SIZE + 1
  35.         return product(range(x1, x2), range(y1, y2))
  36.  
  37.     def query(s, rect):
  38.         items = []
  39.         for cell in s._rect_cells(rect):
  40.             items.extend(s.grid[1 - s.currentGrid].get(cell, ()))
  41.         return items
  42.  
  43.     def query_point(s, pos):
  44.         x, y = pos // SPATIAL_GRID_SIZE
  45.         return s.grid[1 - s.currentGrid].get((x, y), ())
  46.  
  47. GRAVITY = 0.1
  48.  
  49. BALL_RADIUS = 15
  50. BALL_SIZE = pygame.math.Vector2(BALL_RADIUS)
  51. BALL_SIZE_DOUBLE = BALL_SIZE * 2
  52. BALL_COLOR = (34, 128, 75)
  53.  
  54. class ball:
  55.     def __init__(s, x, y):
  56.         s.pos = pygame.math.Vector2(x, y)
  57.         s.velocity = pygame.math.Vector2(0, 0)
  58.  
  59.     def rect(s):
  60.         return pygame.Rect(s.pos - BALL_SIZE, BALL_SIZE_DOUBLE)
  61.  
  62.     def update(s, sh):
  63.         s.pos += s.velocity
  64.         s.velocity.y += GRAVITY
  65.         if s.pos.y > DH - BALL_RADIUS:
  66.             s.pos.y = DH - BALL_RADIUS
  67.         sh.insert(s)
  68.  
  69. DW, DH = 1280, 720
  70. HDW, HDH = DW // 2, DH // 2
  71. DR = pygame.Rect((0, 0), (DW, DH))
  72. pygame.init()
  73. PD = pygame.display.set_mode(DR.size)
  74.  
  75. sh = SpatialHash()
  76.  
  77. BALL_COUNT = 1000
  78. balls = []
  79. for index in range(BALL_COUNT):
  80.     newBall = ball(
  81.         random.randint(0, DW),
  82.         random.randint(0, DH)
  83.     )
  84.     balls.append(newBall)
  85.     sh.insert(newBall)
  86.  
  87. exit = False
  88.  
  89. while True:
  90.     for e in pygame.event.get():
  91.         if e.type == pygame.QUIT:
  92.             exit = True
  93.  
  94.     if exit or pygame.key.get_pressed()[pygame.K_ESCAPE]: break
  95.  
  96.     PD.fill((0, 0, 0))
  97.  
  98.     sh.switchGrid()
  99.     for b in balls:
  100.         pygame.draw.circle(PD, BALL_COLOR, b.pos, BALL_RADIUS)
  101.         query = b.rect().inflate(BALL_SIZE)
  102.         collisionPoints = sh.query_point(b.pos)
  103.         for p in collisionPoints:
  104.             if p == b: continue
  105.             if b.pos.distance_to(p.pos) <= BALL_RADIUS * 2:
  106.                 angle = math.atan2(p.pos.y - b.pos.y, p.pos.x - b.pos.x)
  107.                 p.pos = pygame.math.Vector2(
  108.                     b.pos.x + math.cos(angle) * BALL_RADIUS * 2,
  109.                     b.pos.y + math.sin(angle) * BALL_RADIUS * 2
  110.                 )
  111.         b.update(sh)
  112.  
  113.     pygame.display.update()
  114.     #pygame.time.Clock().tick(60)
Add Comment
Please, Sign In to add comment