+"""Optimized collision detection functions."""
+
+def overlaps(a, b):
+ """Return true if two circles are overlapping.
+
+ Usually, you'll want to use the 'collides' method instead, but
+ this one can be useful for just checking to see if the player has
+ entered an area or hit a stationary oject.
+
+ (This function is optimized.)
+
+ """
+
+ cdef float ax
+ cdef float bx
+ cdef float ay
+ cdef float by
+ cdef float dx
+ cdef float dy
+ cdef float radius_a
+ cdef float radius_b
+ cdef float radius
+
+ ax = a.x
+ bx = b.x
+ ay = a.y
+ by = b.y
+
+ dx = ax - bx
+ dy = ay - by
+ try:
+ radius = a.radius
+ except AttributeError:
+ radius = 0.5
+ try:
+ radius += b.radius
+ except AttributeError:
+ radius += 0.5
+
+ return dx * dx + dy * dy <= radius * radius
+
+
+cdef int _collides(float xa, float xb, float ya, float yb,
+ float pxa, float pxb, float pya, float pyb,
+ float radius_a, float radius_b):
+
+ """Check collision for two moving circles."""
+
+ cdef float dir_x
+ cdef float dir_y
+
+ cdef float diff_x
+ cdef float diff_y
+ cdef float dist_x
+ cdef float dist_y
+
+ cdef float dx
+ cdef float dy
+ cdef float t
+
+ cdef float radius
+
+ radius = radius_a + radius_b
+
+ # Translate b's final position to be relative to a's start.
+ # And now, circle/line collision.
+ dir_x = pxa + (xb - xa) - pxb
+ dir_y = pya + (yb - ya) - pyb
+
+ if (dir_x < 0.0001 and dir_x > -0.0001
+ and dir_y < 0.0001 and dir_y > -0.0001):
+ # b did not move relative to a, so do point/circle.
+ dx = pxb - pxa
+ dy = pyb - pya
+ return dx * dx + dy * dy < radius * radius
+
+ diff_x = pxa - pxb
+ diff_y = pya - pyb
+
+ # dot(diff, dir) / dot(dir, dir)
+ t = (diff_x * dir_x + diff_y * dir_y) / (dir_x * dir_x + dir_y * dir_y)
+ if t < 0:
+ t = 0
+ elif t > 1:
+ t = 1
+
+ dist_x = pxa - (pxb + dir_x * t)
+ dist_y = pya - (pyb + dir_y * t)
+
+ # dist_sq < radius_sq
+ return dist_x * dist_x + dist_y * dist_y <= radius * radius
+
+def collides(a, b):
+ """Return true if the two moving circles collide.
+
+ a and b should have the following attributes:
+
+ x, y - required, current position
+ px, py - not required, defaults to x, y, previous frame position
+ radius - not required, defaults to 0.5
+
+ (This function is optimized.)
+
+ """
+ cdef float xa
+ cdef float xb
+ cdef float ya
+ cdef float yb
+
+ cdef float pxa
+ cdef float pya
+ cdef float pxb
+ cdef float pyb
+
+ cdef float radius_a
+ cdef float radius_b
+
+ xa = a.x
+ xb = b.x
+ ya = a.y
+ yb = b.y
+
+ radius_a = getattr3(a, 'radius', 0.5)
+ radius_b = getattr3(b, 'radius', 0.5)
+
+ pxa = getattr3(a, 'px', xa)
+ pya = getattr3(a, 'py', ya)
+ pxb = getattr3(b, 'px', xb)
+ pyb = getattr3(b, 'py', yb)
+
+ return _collides(xa, xb, ya, yb, pxa, pxb, pya, pyb, radius_a, radius_b)
+
+def collides_all(a, others):
+ """Filter the second argument to those that collide with the first.
+
+ This is equivalent to filter(lambda o: collides(a, o), others),
+ but is much faster when the compiled extension is available (which
+ it is currently).
+
+ """
+ cdef float xa
+ cdef float xb
+ cdef float ya
+ cdef float yb
+
+ cdef float pxa
+ cdef float pya
+ cdef float pxb
+ cdef float pyb
+
+ cdef float radius_a
+ cdef float radius_b
+
+ cdef list bs
+ cdef int length
+
+ cdef list colliding
+
+ cdef int coll
+
+ colliding = []
+
+ xa = a.x
+ ya = a.y
+ radius_a = getattr3(a, 'radius', 0.5)
+ pxa = getattr3(a, 'px', xa)
+ pya = getattr3(a, 'py', ya)
+
+ bs = list(others)
+ length = len(bs)
+
+ for 0 <= i < length:
+ b = others[i]
+ xb = b.x
+ yb = b.y
+ radius_b = getattr3(b, 'radius', 0.5)
+ pxb = getattr3(b, 'px', xb)
+ pyb = getattr3(b, 'py', yb)
+
+ if _collides(xa, xb, ya, yb, pxa, pxb, pya, pyb, radius_a, radius_b):
+ colliding.append(b)
+ return colliding