2009
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.

1 comment so far

Add Your Comment
  1. Cool! :) thanks for taking the time to post this. been learning and was looking for something like this.