Basic static/moving circle collisions. (Fixes issue #5)
authorJoe Wreschnig <joe.wreschnig@gmail.com>
Sun, 21 Mar 2010 00:44:17 +0000 (17:44 -0700)
committerJoe Wreschnig <joe.wreschnig@gmail.com>
Sun, 21 Mar 2010 00:44:17 +0000 (17:44 -0700)
bulletml-runner
bulletml/__init__.py
bulletml/collision.py [new file with mode: 0644]

index faaf9e0..25fa50b 100755 (executable)
@@ -8,6 +8,7 @@ import pygame
 
 import bulletml
 import bulletml.bulletyaml
 
 import bulletml
 import bulletml.bulletyaml
+from bulletml.collision import collides
 
 try:
     import yaml
 
 try:
     import yaml
@@ -82,10 +83,10 @@ def main(argv):
             target.y /= 2
 
             if not paused or go:
             target.y /= 2
 
             if not paused or go:
-
+                lactive = list(active)
                 start = time.time()
                 count = len(active)
                 start = time.time()
                 count = len(active)
-                for obj in list(active):
+                for obj in lactive:
                     new = obj.step()
                     total += len(new)
                     active.update(new)
                     new = obj.step()
                     total += len(new)
                     active.update(new)
@@ -93,6 +94,7 @@ def main(argv):
                         or not (-50 < obj.x < 350)
                         or not (-50 < obj.y < 350)):
                         active.remove(obj)
                         or not (-50 < obj.x < 350)
                         or not (-50 < obj.y < 350)):
                         active.remove(obj)
+                    collides(obj, lactive[0])
                 elapsed = time.time() - start
 
                 frames += 1
                 elapsed = time.time() - start
 
                 frames += 1
index 8ab4ec2..2a825cd 100644 (file)
@@ -9,6 +9,9 @@ renderer-agnostic.
 In addition to the standard BulletML XML format, this module supports
 an equivalent YAML format. See bulletml.bulletyaml for more details.
 
 In addition to the standard BulletML XML format, this module supports
 an equivalent YAML format. See bulletml.bulletyaml for more details.
 
+Finally, two simple collision routines are provided, bulletml.overlaps
+for stationary circles and bulletml.collides for moving circles.
+
 More information is available at the BulletML homepage,
 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html, or the
 python-bullet homepage, http://code.google.com/p/python-bulletml/.
 More information is available at the BulletML homepage,
 http://www.asahi-net.or.jp/~cs8k-cyu/bulletml/index_e.html, or the
 python-bullet homepage, http://code.google.com/p/python-bulletml/.
@@ -33,9 +36,11 @@ attributes that can be used to influence it.
 
 from bulletml.parser import BulletML
 from bulletml.impl import Bullet
 
 from bulletml.parser import BulletML
 from bulletml.impl import Bullet
+from bulletml.collision import overlaps, collides
 
 VERSION = (0, 1)
 VERSION_STRING = ".".join(map(str, VERSION))
 
 
 VERSION = (0, 1)
 VERSION_STRING = ".".join(map(str, VERSION))
 
-__all__ = ["VERSION", "VERSION_STRING", "Bullet", "BulletML"]
+__all__ = ["VERSION", "VERSION_STRING", "Bullet", "BulletML",
+           "overlaps", "collides"]
 
 
diff --git a/bulletml/collision.py b/bulletml/collision.py
new file mode 100644 (file)
index 0000000..f185745
--- /dev/null
@@ -0,0 +1,81 @@
+"""Simple collision check.
+
+This module provides simple collision checking appropriate for
+shmups. It provides a routine to check whether two moving circles
+collided during the past frame.
+
+Basic Usage:
+
+    from bulletml.collision import collides
+
+    for bullet in bullets:
+        if collides(player, bullet): ... # Kill the player.
+"""
+
+from __future__ import division
+
+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.
+    """
+
+    dx = a.x - b.x
+    dy = a.y - b.y
+    radius = getattr(a, 'radius', 0.5) + getattr(b, 'radius', 0.5)
+
+    return dx * dx + dy * dy <= 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
+
+    """
+    # Current locations.
+    xa = a.x
+    xb = b.x
+    ya = a.y
+    yb = b.y
+
+    # Treat b as a point, we only need one radius.
+    radius = getattr(a, 'radius', 0.5) + getattr(b, 'radius', 0.5)
+
+    # Previous frame locations.
+    pxa = getattr(a, 'px', xa)
+    pya = getattr(a, 'py', ya)
+    pxb = getattr(b, 'px', xb)
+    pyb = getattr(b, 'py', yb)
+
+    # 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 abs(dir_x) < 0.0001 and abs(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