07.09
I’ve been programming Lago for a while now and got decently far. Since python isn’t really known for its speed, I decided to implement a QuadTree in it, used for drawing and colliding with static objects in the world. In the future I may decide to make it compatible with moving objects, but for now it’s not necessary.
#Ion's Superific QuadTree! Static for now. LEAF_MAX = 1 class Rect: def __init__( self, x, y, width, height): self.x = x self.y = y self.width = width self.height = height self.right = self.x + self.width self.bottom = self.y + self.height self.centerx = self.x + (self.width/2) self.centery = self.y + (self.height/2) def testIntersect( self, other ): self.right = self.x + self.width self.bottom = self.y + self.height if self.right < other.x: return False if self.x > other.right: return False if self.y > other.bottom: return False if self.bottom < other.y: return False return True def testContains( self, other ): self.right = self.x + self.width self.bottom = self.y + self.height if other.right > self.right: return False if other.x < self.x: return False if other.y < self.y: return False if other.bottom > self.bottom: return False return True def colliderect( self, rect ): #pygame compatibility return self.testIntersect( rect ) def contains( self, rect ): #pygame compatibility return self.testContains( rect ) def getTuple( self ): return (self.x, self.y, self.width, self.height) class QuadTree: def __init__( self, x, y, width, height ): self.rect = Rect( x, y, width, height ) self.oblist = [] self.subnode = [] def addObject( self, ob ): self.oblist.append( ob ) if len( self.oblist ) > LEAF_MAX: halfwidth = self.rect.width/2 halfheight = self.rect.height/2 if len( self.subnode ) == 0: self.subnode.append( QuadTree( self.rect.x , self.rect.y, halfwidth, halfheight) ) # upper left self.subnode.append( QuadTree( self.rect.x + halfwidth, self.rect.y, halfwidth, halfheight) ) #upper right self.subnode.append( QuadTree( self.rect.x , self.rect.y + halfheight, halfwidth, halfheight) ) # lower left self.subnode.append( QuadTree( self.rect.x + halfwidth, self.rect.y + halfheight, halfwidth, halfheight) ) #lower right kill = [] for ob in self.oblist: for sub in self.subnode: if sub.rect.contains( ob.rect ): #object is contained in that node sub.addObject( ob ) kill.append( ob ) break for ob in kill: self.oblist.remove( ob ) def getInBounds( self, rect ): total = [] for ob in self.oblist: total.append( ob ) for sub in self.subnode: if rect.colliderect( sub.rect ): total += sub.getInBounds( rect ) return total def getAll( self ): total = [] for ob in self.oblist: total.append( ob ) for sub in self.subnode: total += sub.getAll( ) return total
It’s compatible with PyGame’s rect class if you want to use that. Simply make a quadtree and give it a size, then call addObject() on objects that have a rect attribute. It provides some optimization at least. Then you call getInBounds( ) to get all of the objects that are mostly in the rectangle you give it (more than what’s in the bounds, but never less).
Now I’m going to learn you some basic AABB collision. AABB stands for Axis Aligned Bounding Box, which is the simplest collision you can ask for. So think 2D rectangles. The first thing you need to is detect the collision between two rectangles. Perhaps surprisingly, its easier try and detect whether they’re not colliding. For example, if the right side of the first rectangle is less than the left side of the second rectangle, then they must not be colliding. The same with the other 3 sides.
So once you see if they’re colliding, you have to see how deep the rectangle is in the other rectangle. This depends on the first rectangles velocity. If its moving right, then you have to consider how deep the rectangle is in from its right side. And then the same with the other 3 sides.
Finally, you move the object along the smaller of the two depths (not both). But the thing is, if that depth is bigger than the velocity on that axis, then you want to move it along the other depth. Anyways the code probably explains it better than I can.
class CollisionMan: def __init__( self ): self.bodylist = [] def addBody( self, body ): self.bodylist.append( body ) def collideBody( self, a ): #simple collision detection a.rect.x += a.velx a.rect.y += a.vely for b in self.bodylist: if a != b: if a.rect.colliderect(b.rect): dx = 0 dy = 0 #find the intersection depth for each axis x and y if a.velx > 0: #moving right dx = -( a.rect.right - b.rect.left ) elif a.velx < 0: # moving left dx = ( b.rect.right - a.rect.left ) if a.vely > 0: #moving down dy = -( a.rect.bottom - b.rect.top ) elif a.vely < 0: #moving up dy = ( b.rect.bottom - a.rect.top ) # I'm a genius! Kinda. The basic algorithm here is to use to find the smallest # intersection depth, but with a twist: you never want to move the object passed its original position, # that is, the interesection depth must be less than (or equal to) the velocity on that axis. # So, at worst, an object can end up back in its original position, before the velocity shifted it. if dy == 0: a.rect.x += dx elif dx == 0: a.rect.y += dy elif abs(dx) < abs( dy ): if abs( dx ) <= abs( a.velx ): a.rect.x += dx else: a.rect.y += dy else: if abs( dy ) <= abs( a.vely ): a.rect.y += dy else: a.rect.x += dx
That’ll do. Add objects with a rect attribute via addObject, then call collideBody on an object in order to move it out of collision for every object in it. It’ll probably need to be modified for moving objects, but seems to work well for now. Hope this helps somebody.
Cool!
thanks for taking the time to post this. been learning and was looking for something like this.