Collision of rotated rectangles without pygame
Martin Manns
mmanns at gmx.net
Sun Dec 5 17:49:36 EST 2010
Hello,
I am looking for a Python library for 2D collision checks of rotated
rectangles. Currently, I have found vizier 0.5b that is based on pygame.
Since I do not want to add a pygame dependency to my app, I replaced the
pygame.rect.Rect by a wxPython wx.Rect (see code below).
However, collision checks do not work correctly, i. e. identical rects
are not found to be colliding:
$ python
Python 2.6.6 (r266:84292, Nov 28 2010, 18:42:58)
[GCC 4.4.5] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import xrect
>>> r=xrect.RotoRect(10,10,30,20,angle=34)
>>> s=xrect.RotoRect(10,10,30,20,angle=34)
>>> r.rotocollide(s)
False
>>>
Is my replacement of the rectangle object wrong or is vizier not
working correctly with pygame as well?
Do you know of any other pure Python library for rotated rectangle
collision checks?
Cheers
Martin
----------------------------------------
from __future__ import division
import math
import wx
##############################################################################
cos_table = dict([(deg, math.cos(math.radians(deg))) for deg in xrange(360)])
sin_table = dict([(deg, math.sin(math.radians(deg))) for deg in xrange(360)])
class RotoRect(wx.Rect):
def __init__(self, *a, **kw):
self.deg = kw.pop('angle')
wx.Rect.__init__(self, *a, **kw)
# pygame rect compatibility for wx.Rect
def get_center(self):
return self.centerx, self.centery
def set_center(self, center):
self.centerx, self.centery = center
def get_centerx(self):
return self.x + self.width / 2
def set_centerx(self, centerx):
self.SetX(centerx - self.width / 2)
def get_centery(self):
return self.y + self.height / 2
def set_centery(self, centery):
self.SetY(centery - self.height / 2)
def get_topleft(self):
return self.GetTopLeft()
def set_topleft(self, p):
self.SetTopLeft(p)
def get_topright(self):
return self.GetTopRight()
def set_topright(self, p):
self.SetTopRight(p)
center = property(get_center, set_center)
centerx = property(get_centerx, set_centerx)
centery = property(get_centery, set_centery)
topleft = property(get_topleft, set_topleft)
topright = property(get_topright, set_topright)
# Now for the vizier code
def rotate(self, point, origin = 0):
"""Returns coords of point p rotated self.theta radians with
the rectangle around its center
"""
if not origin: origin = self.center
p_x = point[0]
p_y = point[1]
o_x = origin[0]
o_y = origin[1]
costheta = cos_table[self.deg]
sintheta = sin_table[self.deg]
return ((o_x + costheta * (p_x - o_x)) - (sintheta * (p_y - o_y)),
(o_y + sintheta * (p_x - o_x)) + (costheta * (p_y - o_y)))
def rotoxt(self, a, b):
"""Returns the y extremity xt of rect between self.rect"""
a_x = a[0]
a_y = a[1]
b_x = b[0]
b_y = b[1]
#calculate difference between self.left and b_x
dxl = self.left - b_x
#calculate difference between self.right and b_x
dxr = self.right - b_x
#if b_x isn't between self.left and self.right
if (dxl * dxr) > 0:
#if dxl < 1, b_x is on the right
if (dxl < 0):
#xt = (m * dxr) + b_y
xt = ((((b_y - (-a_y)) / (b_x - (-a_x))) * dxr) + b_y)
else:
#else b_x is on the left
#xt = (m * dxl) + b_y
xt = ((((b_y - a_y) / (b_x - a_x)) * dxl) + b_y)
return xt
else:
#else b_x is between self.left and self.right, and xt = b_y
return b_y
def rotocollide(self, rect):
"""Check for collision between self.rect and rect"""
#initialize collision to False
col = False
#transforming the plane to set rect's center to the origin
transplane = rect.center
#set rect's center to the origin
rect.center = (0, 0)
#transform self.rect's center by the transplane amount
self.center = (self.centerx - transplane[0],
self.centery - transplane[1])
#transforming the plane to set self.rect's theta to 0
transdeg = self.deg
#set self.rect's theta to 0
self.deg = 0
#transform rect's theta by the transtheta amount
rect.deg -= transdeg
#determine which vertice is min/max x/y NOTE:
# a = left/right, b = top/bottom
if (sin_table[rect.deg] * cos_table[rect.deg]) > 0:
#a = extreme left/right, b = extreme top/bottom
a, b = rect.topright, rect.topleft
else:
a, b = rect.topleft, rect.topright
#determine if a.x is min/max
if sin_table[rect.deg] < 0:
#ensure a is always max
a = -a[0], -a[1]
a_x = a[0]
negb = -b[0], -b[1]
#check that range of rect (-a.x, a.x) overlaps range of
#self.rect (self.left, self.right)
if (a_x >= self.left) and (self.right >= -a_x):
#get the first extremity
xt1 = self.rotoxt(a, b)
#get the other extermity
xt2 = self.rotoxt(a, negb)
#check for an intersection between the two extremities and
#self.rect's top/bottom
col = (((xt1 >= self.top) and (self.bottom >= xt2)) or
((xt2 >= self.top) and (self.bottom >= xt1)))
#retransform rect.center
rect.center = transplane
#retransform self.rect.center
self.center = (self.centerx + transplane[0],
self.centery + transplane[1])
#retransform self.theta
self.deg = transdeg
#retransform rect.theta
rect.deg += transdeg
#return results of collision test
return col
@property
def rotox(self):
return self.rotate(self.topleft)[0]
@property
def rotoy(self):
return self.rotate(self.topleft)[1]
@property
def rotoleft(self):
return self.rotate(self.left)
@property
def rotoright(self):
return self.rotate(self.right)
@property
def rototop(self):
return self.rotate(self.top)
@property
def rotobottom(self):
return self.rotate(self.bottom)
@property
def rototopleft(self):
return self.rotate(self.topleft)
@property
def rototopright(self):
return self.rotate(self.topright)
@property
def rotobottomright(self):
return self.rotate(self.bottomright)
@property
def rotobottomleft(self):
return self.rotate(self.bottomleft)
@property
def rotomidleft(self):
return self.rotate(self.midleft)
@property
def rotomidright(self):
return self.rotate(self.midright)
@property
def rotomidtop(self):
return self.rotate(self.midtop)
@property
def rotomidbottom(self):
return self.rotate(self.midbottom)
More information about the Python-list
mailing list