1#!/usr/bin/env python 2""" pygame.examples.mask 3 4A pygame.mask collision detection production. 5 6 7 8 9Brought 10 11 to 12 you 13 by 14 15 the 16 17pixels 18 0000000000000 19 and 20 111111 21 22 23This is 32 bits: 24 11111111111111111111111111111111 25 26There are 32 or 64 bits in a computer 'word'. 27Rather than using one word for a pixel, 28the mask module represents 32 or 64 pixels in one word. 29As you can imagine, this makes things fast, and saves memory. 30 31Compute intensive things like collision detection, 32and computer vision benefit greatly from this. 33 34 35This module can also be run as a stand-alone program, excepting 36one or more image file names as command line arguments. 37""" 38 39import sys 40import os 41import random 42 43import pygame as pg 44 45 46def maskFromSurface(surface, threshold=127): 47 return pg.mask.from_surface(surface, threshold) 48 49 50def vadd(x, y): 51 return [x[0] + y[0], x[1] + y[1]] 52 53 54def vsub(x, y): 55 return [x[0] - y[0], x[1] - y[1]] 56 57 58def vdot(x, y): 59 return x[0] * y[0] + x[1] * y[1] 60 61 62class Sprite: 63 def __init__(self, surface, mask=None): 64 self.surface = surface 65 if mask: 66 self.mask = mask 67 else: 68 self.mask = maskFromSurface(self.surface) 69 self.setPos([0, 0]) 70 self.setVelocity([0, 0]) 71 72 def setPos(self, pos): 73 self.pos = [pos[0], pos[1]] 74 75 def setVelocity(self, vel): 76 self.vel = [vel[0], vel[1]] 77 78 def move(self, dr): 79 self.pos = vadd(self.pos, dr) 80 81 def kick(self, impulse): 82 self.vel[0] += impulse[0] 83 self.vel[1] += impulse[1] 84 85 def collide(self, s): 86 """Test if the sprites are colliding and 87 resolve the collision in this case.""" 88 offset = [int(x) for x in vsub(s.pos, self.pos)] 89 overlap = self.mask.overlap_area(s.mask, offset) 90 if overlap == 0: 91 return 92 """Calculate collision normal""" 93 nx = self.mask.overlap_area( 94 s.mask, (offset[0] + 1, offset[1]) 95 ) - self.mask.overlap_area(s.mask, (offset[0] - 1, offset[1])) 96 ny = self.mask.overlap_area( 97 s.mask, (offset[0], offset[1] + 1) 98 ) - self.mask.overlap_area(s.mask, (offset[0], offset[1] - 1)) 99 if nx == 0 and ny == 0: 100 """One sprite is inside another""" 101 return 102 n = [nx, ny] 103 dv = vsub(s.vel, self.vel) 104 J = vdot(dv, n) / (2 * vdot(n, n)) 105 if J > 0: 106 """Can scale up to 2*J here to get bouncy collisions""" 107 J *= 1.9 108 self.kick([nx * J, ny * J]) 109 s.kick([-J * nx, -J * ny]) 110 return 111 112 # """Separate the sprites""" 113 # c1 = -overlap/vdot(n,n) 114 # c2 = -c1/2 115 # self.move([c2*nx,c2*ny]) 116 # s.move([(c1+c2)*nx,(c1+c2)*ny]) 117 118 def update(self, dt): 119 self.pos[0] += dt * self.vel[0] 120 self.pos[1] += dt * self.vel[1] 121 122 123def main(*args): 124 """Display multiple images bounce off each other using collision detection 125 126 Positional arguments: 127 one or more image file names. 128 129 This pg.masks demo will display multiple moving sprites bouncing 130 off each other. More than one sprite image can be provided. 131 """ 132 133 if len(args) == 0: 134 raise ValueError("Require at least one image file name: non given") 135 print("Press any key to quit") 136 pg.init() 137 screen = pg.display.set_mode((640, 480)) 138 images = [] 139 masks = [] 140 for impath in args: 141 images.append(pg.image.load(impath).convert_alpha()) 142 masks.append(maskFromSurface(images[-1])) 143 144 numtimes = 10 145 import time 146 147 t1 = time.time() 148 for x in range(numtimes): 149 unused_mask = maskFromSurface(images[-1]) 150 t2 = time.time() 151 152 print("python maskFromSurface :%s" % (t2 - t1)) 153 154 t1 = time.time() 155 for x in range(numtimes): 156 unused_mask = pg.mask.from_surface(images[-1]) 157 t2 = time.time() 158 159 print("C pg.mask.from_surface :%s" % (t2 - t1)) 160 161 sprites = [] 162 for i in range(20): 163 j = i % len(images) 164 s = Sprite(images[j], masks[j]) 165 s.setPos( 166 ( 167 random.uniform(0, screen.get_width()), 168 random.uniform(0, screen.get_height()), 169 ) 170 ) 171 s.setVelocity((random.uniform(-5, 5), random.uniform(-5, 5))) 172 sprites.append(s) 173 pg.time.set_timer(pg.USEREVENT, 33) 174 while 1: 175 event = pg.event.wait() 176 if event.type == pg.QUIT: 177 return 178 elif event.type == pg.USEREVENT: 179 180 # Do both mechanics and screen update 181 screen.fill((240, 220, 100)) 182 for i, sprite in enumerate(sprites): 183 for j in range(i + 1, len(sprites)): 184 sprite.collide(sprites[j]) 185 for s in sprites: 186 s.update(1) 187 if s.pos[0] < -s.surface.get_width() - 3: 188 s.pos[0] = screen.get_width() 189 elif s.pos[0] > screen.get_width() + 3: 190 s.pos[0] = -s.surface.get_width() 191 if s.pos[1] < -s.surface.get_height() - 3: 192 s.pos[1] = screen.get_height() 193 elif s.pos[1] > screen.get_height() + 3: 194 s.pos[1] = -s.surface.get_height() 195 screen.blit(s.surface, s.pos) 196 pg.display.update() 197 elif event.type == pg.KEYDOWN: 198 return 199 200 201if __name__ == "__main__": 202 if len(sys.argv) < 2: 203 print("Usage: mask.py <IMAGE> [<IMAGE> ...]") 204 print("Let many copies of IMAGE(s) bounce against each other") 205 print("Press any key to quit") 206 main_dir = os.path.split(os.path.abspath(__file__))[0] 207 imagename = os.path.join(main_dir, "data", "chimp.png") 208 main(imagename) 209 210 else: 211 main(*sys.argv[1:]) 212 pg.quit() 213