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