"""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 radius_a = getattr3(a, 'radius', 0.5) radius_b = getattr3(b, 'radius', 0.5) radius = radius_a + radius_b 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