3 #define STR_AND_SIZE(s) s, sizeof(s) - 1
4 #define DOT(x1, y1, x2, y2) ((x1) * (x2) + (y1) * (y2))
5 #define NEARZERO(d) ((d) < 0.0001 && (d) > -0.0001)
7 static const char *s_pchModDoc
= "Optimized collision detection functions.";
9 static PyObject
*s_ppykX
;
10 static PyObject
*s_ppykY
;
11 static PyObject
*s_ppykPX
;
12 static PyObject
*s_ppykPY
;
13 static PyObject
*s_ppykRadius
;
15 static const char s_achOverlapsDoc
[] = (
16 "Return true if two circles are overlapping.\n\n"
17 "Usually, you\'ll want to use the \'collides\' method instead, but\n"
18 "this one can be useful for just checking to see if the player has\n"
19 "entered an area or hit a stationary oject.\n\n"
20 "(This function is optimized.)\n\n");
22 static const char s_achCollidesDoc
[] = (
23 "Return true if the two moving circles collide.\n\n"
24 "The circles should have the following attributes:\n\n"
25 " x, y - required, current position\n"
26 " px, py - not required, defaults to x, y, previous frame position\n"
27 " radius - not required, defaults to 0.5\n\n"
28 "(This function is optimized.)\n\n");
30 static const char s_achCollidesAllDoc
[] = (
31 "Filter the second argument to those that collide with the first.\n\n"
32 "This is equivalent to filter(lambda o: collides(a, o), others),\n"
33 "but is much faster when the compiled extension is available (which\n"
34 "it is currently).\n\n");
36 // Get the attributes from a Python moving circle object.
37 static int GetCircle(PyObject
*ppy
, double *pdX
, double *pdY
,
38 double *pdPX
, double *pdPY
, double *pdR
)
45 ppyf
= PyObject_GetAttr(ppy
, s_ppykX
);
48 *pdX
= PyFloat_AsDouble(ppyf
);
52 ppyf
= PyObject_GetAttr(ppy
, s_ppykY
);
55 *pdY
= PyFloat_AsDouble(ppyf
);
59 // Catch X or Y or failure to convert, any one of the four cases
60 // is equally fatal. We don't need to check after each one.
64 ppyf
= PyObject_GetAttr(ppy
, s_ppykPX
);
67 *pdPX
= PyFloat_AsDouble(ppyf
);
78 ppyf
= PyObject_GetAttr(ppy
, s_ppykPY
);
81 *pdPY
= PyFloat_AsDouble(ppyf
);
92 ppyf
= PyObject_GetAttr(ppy
, s_ppykRadius
);
95 *pdR
= PyFloat_AsDouble(ppyf
);
109 static int Collides(double dXA
, double dXB
, double dYA
, double dYB
,
110 double dPXA
, double dPXB
, double dPYA
, double dPYB
,
111 double dRA
, double dRB
)
113 // Translate B's position to be relative to A's start.
114 double dDirX
= dPXA
+ (dXB
- dXA
) - dPXB
;
115 double dDirY
= dPYA
+ (dYB
- dYA
) - dPYB
;
116 // Now A doesn't move. Treat B as a point by summing the radii.
117 double dR
= dRA
+ dRB
;
118 // Now the problem is just circle/line collision.
120 double dDiffX
= dPXA
- dPXB
;
121 double dDiffY
= dPYA
- dPYB
;
123 // B didn't move relative to A, so early-out by doing point/circle.
124 if (NEARZERO(dDirX
) && NEARZERO(dDirY
))
125 return dDiffX
* dDiffX
+ dDiffY
* dDiffY
<= dR
* dR
;
128 double dT
= (DOT(dDiffX
, dDiffY
, dDirX
, dDirY
)
129 / DOT(dDirX
, dDirY
, dDirX
, dDirY
));
132 if (dT
< 0.0) dT
= 0.0;
133 else if (dT
> 1.0) dT
= 1.0;
135 dDistX
= dPXA
- (dPXB
+ dDirX
* dT
);
136 dDistY
= dPYA
- (dPYB
+ dDirY
* dT
);
138 return dDistX
* dDistX
+ dDistY
* dDistY
<= dR
* dR
;
142 static PyObject
*py_overlaps(PyObject
*ppySelf
, PyObject
*ppyArgs
) {
143 double dXA
, dYA
, dPXA
, dPYA
, dRA
;
144 double dXB
, dYB
, dPXB
, dPYB
, dRB
;
145 PyObject
*ppyA
, *ppyB
;
146 if (PyArg_ParseTuple(ppyArgs
, "OO", &ppyA
, &ppyB
)
147 && GetCircle(ppyA
, &dXA
, &dYA
, &dPXA
, &dPYA
, &dRA
)
148 && GetCircle(ppyB
, &dXB
, &dYB
, &dPXB
, &dPYB
, &dRB
))
150 double dX
= dXA
- dXB
;
151 double dY
= dYA
- dYB
;
152 double dR
= dRA
+ dRB
;
154 if (dX
* dX
+ dY
* dY
<= dR
* dR
)
167 static PyObject
*py_collides(PyObject
*ppySelf
, PyObject
*ppyArgs
)
169 double dXA
, dYA
, dPXA
, dPYA
, dRA
;
170 double dXB
, dYB
, dPXB
, dPYB
, dRB
;
171 PyObject
*ppyA
, *ppyB
;
172 if (PyArg_ParseTuple(ppyArgs
, "OO", &ppyA
, &ppyB
)
173 && GetCircle(ppyA
, &dXA
, &dYA
, &dPXA
, &dPYA
, &dRA
)
174 && GetCircle(ppyB
, &dXB
, &dYB
, &dPXB
, &dPYB
, &dRB
))
176 if (Collides(dXA
, dXB
, dYA
, dYB
, dPXA
, dPXB
, dPYA
, dPYB
, dRA
, dRB
))
189 static PyObject
*py_collides_all(PyObject
*ppySelf
, PyObject
*ppyArgs
)
191 double dXA
, dYA
, dPXA
, dPYA
, dRA
;
192 PyObject
*ppyA
, *ppyOthers
;
193 if (PyArg_ParseTuple(ppyArgs
, "OO", &ppyA
, &ppyOthers
)
194 && GetCircle(ppyA
, &dXA
, &dYA
, &dPXA
, &dPYA
, &dRA
))
196 PyObject
*ppyRet
= PyList_New(0);
197 Py_ssize_t pyszLen
= ppyRet
? PySequence_Length(ppyOthers
) : -1;
201 for (sz
= 0; sz
< pyszLen
; sz
++)
203 double dXB
, dYB
, dPXB
, dPYB
, dRB
;
204 PyObject
*ppyB
= PySequence_GetItem(ppyOthers
, sz
);
205 if (!GetCircle(ppyB
, &dXB
, &dYB
, &dPXB
, &dPYB
, &dRB
))
210 else if (Collides(dXA
, dXB
, dYA
, dYB
, dPXA
, dPXB
, dPYA
, dPYB
,
212 PyList_Append(ppyRet
, ppyB
);
224 static struct PyMethodDef s_apymeth
[] = {
225 {"overlaps", py_overlaps
, METH_VARARGS
, s_achOverlapsDoc
},
226 {"collides", py_collides
, METH_VARARGS
, s_achCollidesDoc
},
227 {"collides_all", py_collides_all
, METH_VARARGS
, s_achCollidesAllDoc
},
228 {NULL
, NULL
, 0, NULL
}
231 PyMODINIT_FUNC
init_collision(void)
233 s_ppykX
= PyString_FromStringAndSize(STR_AND_SIZE("x"));
234 s_ppykY
= PyString_FromStringAndSize(STR_AND_SIZE("y"));
235 s_ppykPX
= PyString_FromStringAndSize(STR_AND_SIZE("px"));
236 s_ppykPY
= PyString_FromStringAndSize(STR_AND_SIZE("py"));
237 s_ppykRadius
= PyString_FromStringAndSize(STR_AND_SIZE("radius"));
239 if (s_ppykX
&& s_ppykY
&& s_ppykPX
&& s_ppykPY
&& s_ppykRadius
)
240 Py_InitModule3("bulletml._collision", s_apymeth
, s_pchModDoc
);