--- /dev/null
+#include "Python.h"
+
+#define STR_AND_SIZE(s) s, sizeof(s) - 1
+#define DOT(x1, y1, x2, y2) ((x1) * (x2) + (y1) * (y2))
+#define NEARZERO(d) ((d) < 0.0001 && (d) > -0.0001)
+
+static const char *s_pchModDoc = "Optimized collision detection functions.";
+
+static PyObject *s_ppykX;
+static PyObject *s_ppykY;
+static PyObject *s_ppykPX;
+static PyObject *s_ppykPY;
+static PyObject *s_ppykRadius;
+
+static const char s_achOverlapsDoc[] = (
+ "Return true if two circles are overlapping.\n\n"
+ "Usually, you\'ll want to use the \'collides\' method instead, but\n"
+ "this one can be useful for just checking to see if the player has\n"
+ "entered an area or hit a stationary oject.\n\n"
+ "(This function is optimized.)\n\n");
+
+static const char s_achCollidesDoc[] = (
+ "Return true if the two moving circles collide.\n\n"
+ "The circles should have the following attributes:\n\n"
+ " x, y - required, current position\n"
+ " px, py - not required, defaults to x, y, previous frame position\n"
+ " radius - not required, defaults to 0.5\n\n"
+ "(This function is optimized.)\n\n");
+
+static const char s_achCollidesAllDoc[] = (
+ "Filter the second argument to those that collide with the first.\n\n"
+ "This is equivalent to filter(lambda o: collides(a, o), others),\n"
+ "but is much faster when the compiled extension is available (which\n"
+ "it is currently).\n\n");
+
+// Get the attributes from a Python moving circle object.
+static int GetCircle(PyObject *ppy, double *pdX, double *pdY,
+ double *pdPX, double *pdPY, double *pdR)
+{
+ PyObject *ppyf;
+
+ if (!ppy)
+ return 0;
+
+ ppyf = PyObject_GetAttr(ppy, s_ppykX);
+ if (ppyf)
+ {
+ *pdX = PyFloat_AsDouble(ppyf);
+ Py_DECREF(ppyf);
+ }
+
+ ppyf = PyObject_GetAttr(ppy, s_ppykY);
+ if (ppyf)
+ {
+ *pdY = PyFloat_AsDouble(ppyf);
+ Py_DECREF(ppyf);
+ }
+
+ // Catch X or Y or failure to convert, any one of the four cases
+ // is equally fatal. We don't need to check after each one.
+ if (PyErr_Occurred())
+ return 0;
+
+ ppyf = PyObject_GetAttr(ppy, s_ppykPX);
+ if (ppyf)
+ {
+ *pdPX = PyFloat_AsDouble(ppyf);
+ Py_DECREF(ppyf);
+ if (PyErr_Occurred())
+ return 0;
+ }
+ else
+ {
+ PyErr_Clear();
+ *pdPX = *pdX;
+ }
+
+ ppyf = PyObject_GetAttr(ppy, s_ppykPY);
+ if (ppyf)
+ {
+ *pdPY = PyFloat_AsDouble(ppyf);
+ Py_DECREF(ppyf);
+ if (PyErr_Occurred())
+ return 0;
+ }
+ else
+ {
+ PyErr_Clear();
+ *pdPY = *pdY;
+ }
+
+ ppyf = PyObject_GetAttr(ppy, s_ppykRadius);
+ if (ppyf)
+ {
+ *pdR = PyFloat_AsDouble(ppyf);
+ Py_DECREF(ppyf);
+ if (PyErr_Occurred())
+ return 0;
+ }
+ else
+ {
+ PyErr_Clear();
+ *pdR = 0.5;
+ }
+
+ return 1;
+}
+
+static int Collides(double dXA, double dXB, double dYA, double dYB,
+ double dPXA, double dPXB, double dPYA, double dPYB,
+ double dRA, double dRB)
+{
+ // Translate B's position to be relative to A's start.
+ double dDirX = dPXA + (dXB - dXA) - dPXB;
+ double dDirY = dPYA + (dYB - dYA) - dPYB;
+ // Now A doesn't move. Treat B as a point by summing the radii.
+ double dR = dRA + dRB;
+ // Now the problem is just circle/line collision.
+
+ double dDiffX = dPXA - dPXB;
+ double dDiffY = dPYA - dPYB;
+
+ // B didn't move relative to A, so early-out by doing point/circle.
+ if (NEARZERO(dDirX) && NEARZERO(dDirY))
+ return dDiffX * dDiffX + dDiffY * dDiffY <= dR * dR;
+ else
+ {
+ double dT = (DOT(dDiffX, dDiffY, dDirX, dDirY)
+ / DOT(dDirX, dDirY, dDirX, dDirY));
+ double dDistX;
+ double dDistY;
+ if (dT < 0.0) dT = 0.0;
+ else if (dT > 1.0) dT = 1.0;
+
+ dDistX = dPXA - (dPXB + dDirX * dT);
+ dDistY = dPYA - (dPYB + dDirY * dT);
+
+ return dDistX * dDistX + dDistY * dDistY <= dR * dR;
+ }
+}
+
+static PyObject *py_overlaps(PyObject *ppySelf, PyObject *ppyArgs) {
+ double dXA, dYA, dPXA, dPYA, dRA;
+ double dXB, dYB, dPXB, dPYB, dRB;
+ PyObject *ppyA, *ppyB;
+ if (PyArg_ParseTuple(ppyArgs, "OO", &ppyA, &ppyB)
+ && GetCircle(ppyA, &dXA, &dYA, &dPXA, &dPYA, &dRA)
+ && GetCircle(ppyB, &dXB, &dYB, &dPXB, &dPYB, &dRB))
+ {
+ double dX = dXA - dXB;
+ double dY = dYA - dYB;
+ double dR = dRA + dRB;
+
+ if (dX * dX + dY * dY <= dR * dR)
+ {
+ Py_RETURN_TRUE;
+ }
+ else
+ {
+ Py_RETURN_FALSE;
+ }
+ }
+ else
+ return NULL;
+}
+
+static PyObject *py_collides(PyObject *ppySelf, PyObject *ppyArgs)
+{
+ double dXA, dYA, dPXA, dPYA, dRA;
+ double dXB, dYB, dPXB, dPYB, dRB;
+ PyObject *ppyA, *ppyB;
+ if (PyArg_ParseTuple(ppyArgs, "OO", &ppyA, &ppyB)
+ && GetCircle(ppyA, &dXA, &dYA, &dPXA, &dPYA, &dRA)
+ && GetCircle(ppyB, &dXB, &dYB, &dPXB, &dPYB, &dRB))
+ {
+ if (Collides(dXA, dXB, dYA, dYB, dPXA, dPXB, dPYA, dPYB, dRA, dRB))
+ {
+ Py_RETURN_TRUE;
+ }
+ else
+ {
+ Py_RETURN_FALSE;
+ }
+ }
+ else
+ return NULL;
+}
+
+static PyObject *py_collides_all(PyObject *ppySelf, PyObject *ppyArgs)
+{
+ double dXA, dYA, dPXA, dPYA, dRA;
+ PyObject *ppyA, *ppyOthers;
+ if (PyArg_ParseTuple(ppyArgs, "OO", &ppyA, &ppyOthers)
+ && GetCircle(ppyA, &dXA, &dYA, &dPXA, &dPYA, &dRA))
+ {
+ PyObject *ppyRet = PyList_New(0);
+ Py_ssize_t pyszLen = ppyRet ? PySequence_Length(ppyOthers) : -1;
+ if (pyszLen >= 0)
+ {
+ Py_ssize_t sz;
+ for (sz = 0; sz < pyszLen; sz++)
+ {
+ double dXB, dYB, dPXB, dPYB, dRB;
+ PyObject *ppyB = PySequence_GetItem(ppyOthers, sz);
+ if (!GetCircle(ppyB, &dXB, &dYB, &dPXB, &dPYB, &dRB))
+ {
+ Py_XDECREF(ppyB);
+ return NULL;
+ }
+ else if (Collides(dXA, dXB, dYA, dYB, dPXA, dPXB, dPYA, dPYB,
+ dRA, dRB))
+ PyList_Append(ppyRet, ppyB);
+ Py_DECREF(ppyB);
+ }
+ return ppyRet;
+ }
+ else
+ return NULL;
+ }
+ else
+ return NULL;
+}
+
+static struct PyMethodDef s_apymeth[] = {
+ {"overlaps", py_overlaps, METH_VARARGS, s_achOverlapsDoc },
+ {"collides", py_collides, METH_VARARGS, s_achCollidesDoc },
+ {"collides_all", py_collides_all, METH_VARARGS, s_achCollidesAllDoc },
+ {NULL, NULL, 0, NULL}
+};
+
+PyMODINIT_FUNC init_collision(void)
+{
+ s_ppykX = PyString_FromStringAndSize(STR_AND_SIZE("x"));
+ s_ppykY = PyString_FromStringAndSize(STR_AND_SIZE("y"));
+ s_ppykPX = PyString_FromStringAndSize(STR_AND_SIZE("px"));
+ s_ppykPY = PyString_FromStringAndSize(STR_AND_SIZE("py"));
+ s_ppykRadius = PyString_FromStringAndSize(STR_AND_SIZE("radius"));
+
+ if (s_ppykX && s_ppykY && s_ppykPX && s_ppykPY && s_ppykRadius)
+ Py_InitModule3("bulletml._collision", s_apymeth, s_pchModDoc);
+}
+++ /dev/null
-"""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