X-Git-Url: https://git.yukkurigames.com/?p=python-bulletml.git;a=blobdiff_plain;f=bulletml%2F_collision.pyx;fp=bulletml%2F_collision.pyx;h=2011e984a06e1208d3fbe37535d364e0fc51a795;hp=0000000000000000000000000000000000000000;hb=7b73a60799150ec3df407a8a1620a613aad5f59c;hpb=e360de79a855c7c2a1dc80ae940aad00962175ad diff --git a/bulletml/_collision.pyx b/bulletml/_collision.pyx new file mode 100644 index 0000000..2011e98 --- /dev/null +++ b/bulletml/_collision.pyx @@ -0,0 +1,182 @@ +"""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