collision: Docstring should not mention pyrex.
[python-bulletml.git] / bulletml / collision.py
1 """Simple collision check.
2
3 This module provides simple collision checking appropriate for
4 shmups. It provides a routine to check whether two moving circles
5 collided during the past frame.
6
7 An equivalent C-based version will be used automatically if it was
8 compiled and installed with the module. If available, it will be noted
9 in the docstrings for the functions.
10
11 Basic Usage:
12
13 from bulletml.collision import collides
14
15 for bullet in bullets:
16 if collides(player, bullet): ... # Kill the player.
17 """
18
19 from __future__ import division
20
21 def overlaps(a, b):
22 """Return true if two circles are overlapping.
23
24 Usually, you'll want to use the 'collides' method instead, but
25 this one can be useful for just checking to see if the player has
26 entered an area or hit a stationary oject.
27
28 (This function is unoptimized.)
29 """
30
31 dx = a.x - b.x
32 dy = a.y - b.y
33 radius = getattr(a, 'radius', 0.5) + getattr(b, 'radius', 0.5)
34
35 return dx * dx + dy * dy <= radius * radius
36
37 def collides(a, b):
38 """Return true if the two moving circles collide.
39
40 a and b should have the following attributes:
41
42 x, y - required, current position
43 px, py - not required, defaults to x, y, previous frame position
44 radius - not required, defaults to 0.5
45
46 (This function is unoptimized.)
47
48 """
49 # Current locations.
50 xa = a.x
51 xb = b.x
52 ya = a.y
53 yb = b.y
54
55 # Treat b as a point, we only need one radius.
56 radius = getattr(a, 'radius', 0.5) + getattr(b, 'radius', 0.5)
57
58 # Previous frame locations.
59 pxa = getattr(a, 'px', xa)
60 pya = getattr(a, 'py', ya)
61 pxb = getattr(b, 'px', xb)
62 pyb = getattr(b, 'py', yb)
63
64 # Translate b's final position to be relative to a's start.
65 # And now, circle/line collision.
66 dir_x = pxa + (xb - xa) - pxb
67 dir_y = pya + (yb - ya) - pyb
68
69 if abs(dir_x) < 0.0001 and abs(dir_y) < 0.0001:
70 # b did not move relative to a, so do point/circle.
71 dx = pxb - pxa
72 dy = pyb - pya
73 return dx * dx + dy * dy < radius * radius
74
75 diff_x = pxa - pxb
76 diff_y = pya - pyb
77
78 # dot(diff, dir) / dot(dir, dir)
79 t = (diff_x * dir_x + diff_y * dir_y) / (dir_x * dir_x + dir_y * dir_y)
80 if t < 0:
81 t = 0
82 elif t > 1:
83 t = 1
84
85 dist_x = pxa - (pxb + dir_x * t)
86 dist_y = pya - (pyb + dir_y * t)
87
88 # dist_sq < radius_sq
89 return dist_x * dist_x + dist_y * dist_y <= radius * radius
90
91 def collides_all(a, others):
92 """Filter the second argument to those that collide with the first.
93
94 This is equivalent to filter(lambda o: collides(a, o), others),
95 but is much faster when the compiled extension is available (which
96 it is not currently).
97
98 """
99 return filter(lambda o: collides(a, o), others)
100
101 try:
102 from bulletml._collision import collides, overlaps, collides_all
103 except ImportError:
104 pass