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