Advertisement
Guest User

Untitled

a guest
Nov 21st, 2010
82
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 9.78 KB | None | 0 0
  1. #!/usr/bin/env python26
  2. # -*- encoding: utf-8 -*-
  3.  
  4. """
  5. Quadtree intersection collision detection.
  6.  
  7. I do not recommend using this in a production environment _at all_.
  8. """
  9.  
  10. __author__ = "rfw"
  11.  
  12. import Image
  13. from time import clock
  14.  
  15. DEPTH = 4
  16.  
  17. class QuadTree(object):
  18.     def __init__(self, root, origin):
  19.         self.root = root
  20.         self.origin = origin
  21.    
  22.     def collides(self, other):
  23.         return self.root.collides(self.origin, other.root, other.origin)
  24.  
  25. class QuadTreeNode(object):
  26.     """
  27.    An example quadtree node class.
  28.    """
  29.     def __init__(
  30.         self,
  31.         collidable,
  32.         size,
  33.         nw=None,
  34.         ne=None,
  35.         se=None,
  36.         sw=None
  37.     ):
  38.        
  39.         self.collidable = collidable
  40.        
  41.         self.size = size
  42.        
  43.         self.nw = nw
  44.         self.ne = ne
  45.         self.se = se
  46.         self.sw = sw
  47.        
  48.    
  49.     childless = property(lambda self: not (
  50.         self.nw or
  51.         self.ne or
  52.         self.se or
  53.         self.sw
  54.     ))
  55.    
  56.     def __iter__(self):
  57.         """
  58.        Iterate through the quadtree clockwise, starting with northwewst.
  59.        """
  60.         if self.nw: yield self.nw
  61.         if self.ne: yield self.ne
  62.         if self.se: yield self.se
  63.         if self.sw: yield self.sw
  64.    
  65.     def deep_collidable(self):
  66.         """
  67.        Recursively check if the node is collidable at any level.
  68.        
  69.        Automatically memoizes for efficiency.
  70.        """
  71.         if self.childless:
  72.             return self.collidable
  73.        
  74.         if not hasattr(self, "_memoized_deep_collidable"):
  75.             # calculate it
  76.             for child in self:
  77.                 if child.deep_collidable():
  78.                     self._memoized_deep_collidable = True
  79.                     break
  80.                 else:
  81.                     self._memoized_deep_collidable = False
  82.        
  83.         return self._memoized_deep_collidable
  84.    
  85.     def aabb_collides(self, origin, other, otherorigin):
  86.         """
  87.        Perform an AABB collision test to ensure that the quadrant is in the
  88.        right place.
  89.        """
  90.         if origin[0] + self.size[0] < otherorigin[0]: return False
  91.         if origin[0] > otherorigin[0] + other.size[0]: return False
  92.    
  93.         if origin[1] + self.size[1] < otherorigin[1]: return False
  94.         if origin[1] > otherorigin[1] + other.size[1]: return False
  95.        
  96.         return True
  97.    
  98.     def collides(self, origin, other, otherorigin):
  99.         # basic AABB pass
  100.         if not self.aabb_collides(origin, other, otherorigin):
  101.             return False
  102.        
  103.         # one or both of us have no kids
  104.         if self.childless or other.childless:
  105.             return self.deep_collidable() & other.deep_collidable()
  106.        
  107.         # dual quadtree traversal pass (XXX: messy!)
  108.         for node in self:
  109.             if node is self.nw:
  110.                 nodepos = (
  111.                     origin[0],
  112.                     origin[1]
  113.                 )
  114.             elif node is self.ne:
  115.                 nodepos = (
  116.                     origin[0] + node.size[0],
  117.                     origin[1]
  118.                 )
  119.             elif node is self.se:
  120.                 nodepos = (
  121.                     origin[0] + node.size[0],
  122.                     origin[1] + node.size[1]
  123.                 )
  124.             elif node is self.sw:
  125.                 nodepos = (
  126.                     origin[0],
  127.                     origin[1] + node.size[1]
  128.                 )
  129.            
  130.             for othernode in other:
  131.                 if othernode is other.nw:
  132.                     othernodepos = (
  133.                         otherorigin[0],
  134.                         otherorigin[1]
  135.                     )
  136.                 elif node is other.ne:
  137.                     othernodepos = (
  138.                         otherorigin[0] + othernode.size[0],
  139.                         otherorigin[1]
  140.                     )
  141.                 elif othernode is other.se:
  142.                     othernodepos = (
  143.                         otherorigin[0] + othernode.size[0],
  144.                         otherorigin[1] + othernode.size[1]
  145.                     )
  146.                 elif node is other.sw:
  147.                     othernodepos = (
  148.                         otherorigin[0],
  149.                         otherorigin[1] + othernode.size[1]
  150.                     )
  151.                    
  152.                 if node.aabb_collides(nodepos, othernode, othernodepos) and \
  153.                     node.deep_collidable() and \
  154.                     othernode.deep_collidable():
  155.                     return node.collides(nodepos, othernode, othernodepos)
  156.        
  157.         return False
  158.  
  159. def _naive_optimize_quadtree(node):
  160.     """
  161.    Naïvely optimize a quadtree via traversal.
  162.    
  163.    Returns the deepest nodes.
  164.    """
  165.     nodes = []
  166.    
  167.     if node.collidable: # no optimization required
  168.         return [ node ]
  169.    
  170.     if node.nw and \
  171.        node.ne and \
  172.        node.se and \
  173.        node.sw and \
  174.        node.nw.collidable is True and \
  175.        node.ne.collidable is True and \
  176.        node.se.collidable is True and \
  177.        node.sw.collidable is True:
  178.         node.collidable = True
  179.        
  180.         node.nw = None
  181.         node.ne = None
  182.         node.se = None
  183.         node.sw = None
  184.        
  185.         return [ node ]
  186.    
  187.     if node.nw:
  188.         nodes.extend(_naive_optimize_quadtree(node.nw))
  189.    
  190.     if node.ne:
  191.         nodes.extend(_naive_optimize_quadtree(node.ne))
  192.    
  193.     if node.se:
  194.         nodes.extend(_naive_optimize_quadtree(node.se))
  195.    
  196.     if node.sw:
  197.         nodes.extend(_naive_optimize_quadtree(node.sw))
  198.    
  199.     return nodes
  200.  
  201. def _naive_populate_quadtree(node, depth, origin):
  202.     """
  203.    Recursively populate a quadtree, naïvely (do not benchmark!)
  204.    
  205.    It will also attach position metadata which is meaningless in most cases.
  206.    
  207.    Returns the deepest nodes.
  208.    """
  209.     nodes = []
  210.    
  211.     node.position = origin
  212.    
  213.     if depth != 0:
  214.         halfsize = (
  215.             node.size[0] / 2.0,
  216.             node.size[1] / 2.0
  217.         )
  218.        
  219.         # northwest
  220.         node.nw = QuadTreeNode(
  221.             False,
  222.             halfsize
  223.         )
  224.         nodes.extend(
  225.             _naive_populate_quadtree(
  226.                 node.nw,
  227.                 depth - 1,
  228.                 (
  229.                     origin[0],
  230.                     origin[1]
  231.                 )
  232.             )
  233.         )
  234.        
  235.         # northeast
  236.         node.ne = QuadTreeNode(
  237.             False,
  238.             halfsize
  239.         )
  240.         nodes.extend(
  241.             _naive_populate_quadtree(
  242.                 node.ne,
  243.                 depth - 1,
  244.                 (
  245.                     origin[0] + halfsize[0],
  246.                     origin[1]
  247.                 )
  248.             )
  249.         )
  250.        
  251.         # southeast
  252.         node.se = QuadTreeNode(
  253.             False,
  254.             halfsize
  255.         )
  256.         nodes.extend(
  257.             _naive_populate_quadtree(
  258.                 node.se,
  259.                 depth - 1,
  260.                 (
  261.                     origin[0] + halfsize[0],
  262.                     origin[1] + halfsize[1]
  263.                 )
  264.             )
  265.         )
  266.        
  267.         # southwest
  268.         node.sw = QuadTreeNode(
  269.             False,
  270.             halfsize
  271.         )
  272.         nodes.extend(
  273.             _naive_populate_quadtree(
  274.                 node.sw,
  275.                 depth - 1,
  276.                 (
  277.                     origin[0],
  278.                     origin[1] + halfsize[1]
  279.                 )
  280.             )
  281.         )
  282.     else:
  283.         return [ node ]
  284.    
  285.     return nodes
  286.  
  287. def _construct_demo_quadtree(origin, image):
  288.     """
  289.    Construct a demo quadtree from an image (do not benchmark!)
  290.    """
  291.     im = Image.open(image)
  292.    
  293.     # construct a quadtree (very) naïvely
  294.    
  295.     starttime = clock()
  296.     print "Populating quadtree...",
  297.     root = QuadTreeNode(False, im.size)
  298.     deepest = _naive_populate_quadtree(root, DEPTH, origin)
  299.     quadtree = QuadTree(root, origin)
  300.     print "done (%d deepest nodes, took %.2f seconds)." % (
  301.         len(deepest),
  302.         clock() - starttime
  303.     )
  304.    
  305.     starttime = clock()
  306.     print "Coloring quadtree...",
  307.     collidable = set([])
  308.     for i, color in enumerate(im.getdata()):
  309.         x = i % im.size[0]
  310.         y = i // im.size[0]
  311.        
  312.         if color != (0, 0, 0, 0):
  313.             for node in deepest:
  314.                 relpos = (
  315.                     node.position[0] - origin[0],
  316.                     node.position[1] - origin   [1]
  317.                 )
  318.                 if relpos[0] < x < relpos[0] + node.size[0] and \
  319.                    relpos[1] < y < relpos[1] + node.size[1]:
  320.                     node.collidable = True
  321.                     collidable.add(node)
  322.                     break
  323.     print "done (%d nodes set to collidable, took %.2f seconds)." % (
  324.         len(collidable),
  325.         clock() - starttime
  326.     )
  327.    
  328.     starttime = clock()
  329.     # optimize the quadtree
  330.     print "Optimizing quadtree...",
  331.     deepest = _naive_optimize_quadtree(quadtree.root)
  332.     print "done (%d deepest nodes, took %.2f seconds)." % (
  333.         len(deepest),
  334.         clock() - starttime
  335.     )
  336.    
  337.     print ""
  338.    
  339.     return quadtree
  340.  
  341. def main():
  342.     tree = _construct_demo_quadtree((0, 0), "test.png")
  343.     tree2 = QuadTree(tree.root, (-10, -10))
  344.     tree3 = QuadTree(tree.root, (1000, 1000))
  345.    
  346.     starttime = clock()
  347.     print "Performing 1000 true collisions...",
  348.     for i in xrange(1000):
  349.         assert tree.collides(tree2)
  350.     print "done (took %.2f seconds)." % (clock() - starttime)
  351.    
  352.     starttime = clock()
  353.     print "Performing 1000 false collisions...",
  354.     for i in xrange(1000):
  355.         assert not tree.collides(tree3)
  356.     print "done (took %.2f seconds)." % (clock() - starttime)
  357.  
  358. if __name__ == "__main__":
  359.     main()
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement