3

So I'm writing a game. Here's how the collision detection works; there's an invisible grid, and objects (or, rather, their references) are added and removed from cells based on where they are. Collision comparisons are only done between objects in the same cell.

The references are stored in a Python set that is owned by each cell. It has to be a set. When a collision is detected, it's stored in a Collision object with three attributes; the two objects that collided and the time the collision occurred. When all collisions are accounted for, they're sorted by time, then processed.

This is the loop that individual cells use to check for collisions;

#grid.collisions is a list of recorded collisions, and self.objects is the set
#of objects currently in this cell.

for i in self.objects:
    for j in self.objects:
        if id(i) != id(j) and pygame.sprite.collide_rect(i, j):
            grid.collisions.append(Collision(i, j))

The problem is, this compares the same two objects twice; if we had a set {1, 2, 3, 4}, we'd be comparing (1, 2) and (2, 1). Not only that, but these duplicate comparisons are being added into the total list of collisions and thereby breaking the entire system!

I know that I can't index sets, which means I can't do something like for j in range(i, len(self.objects)) where i and j are both integers. How can I get around this limitation to make sure that two objects in the same set are not compared twice? I can't remove the objects in these sets; they are only to be removed if the object leaves the grid cell.

Since these collisions are being handled every frame, I would prefer to avoid creating new objects (the real code doesn't create new Collisions as the sample does, I just simplified it for readability). I could just remove duplicate comparisons, but to compare every collision to every other would be a lot of overhead.

JesseTG
  • 2,025
  • 1
  • 24
  • 48
  • @VinayakKolagi For your information comments doesn't build the reputation. – Ahsan Jul 24 '12 at 05:28
  • 2
    @Ahsan Heh, I love coming across conversations where multiple people are bashing a comment that got taken down. Beauty of crowdsourcing-based filtering :D – jdero Aug 07 '13 at 00:18

3 Answers3

7

If your goal is to just compare all the unique combinations of the set, you could make use of itertools.combinations

from itertools import combinations

for i, j in combinations(self.objects, 2):
    if pygame.sprite.collide_rect(i, j):
        grid.collisions.append(Collision(i, j))

Example:

aSet = set([1,2,3,4])
list(combinations(aSet, 2))
# [(1, 2), (1, 3), (1, 4), (2, 3), (2, 4), (3, 4)]

combinations produces a generator which is pretty efficient, compared to managing multiple indexs and temporary lists

jdi
  • 90,542
  • 19
  • 167
  • 203
  • You beat me to this answer. I don't think there is a faster way to solve this problem in Python. – steveha Jul 24 '12 at 05:09
  • @steveha: There might be a faster solution in numpy/scipy but I am not sure :-) – jdi Jul 24 '12 at 05:24
  • 2
    `numpy` has fast matrix operations, but nothing special for permutations or combinations. And `itertools` is designed for speed. You can kind of make it a rule: anytime you want to make something faster, think if there is a way to use `itertools`. – steveha Jul 24 '12 at 18:29
  • Really, now? Better familiarize myself with it, then. Thanks! – JesseTG Jul 24 '12 at 18:35
  • @JesseTG: I have found some amazing solutions from `itertools`. It is one of the best modules in the python standard lib. – jdi Jul 24 '12 at 18:39
  • Something that came to mind only years later; what's the run-time complexity of this? – JesseTG Apr 13 '16 at 19:18
  • 1
    @JesseTG, it kind of says it on the python doc link. The combination loop is O(n!). The list append is O(1) when it happens. I don't know what the complexity of the collision test is. – jdi Apr 13 '16 at 19:28
  • Ah, must not have noticed it the last time I looked. Derp. Thanks! (Also, the actual collision test is O(1), because it's arithmetic on some rectangles) – JesseTG Apr 13 '16 at 19:35
1

How about making a list?

objects=list(self.objects)
for i in range(len(objects)):
  for j in range(i+1,len(objects)):
     if pygame.sprite.collide_rect(objects[i], objects[j]):
       grid.collisions.append(Collision(objects[i], objects[j]))

Here's another alternative:

objects=[]
for i in range(self.objects):
  for j in objects:
    if pygame.sprite.collide_rect(i, j):
      grid.collisions.append(Collision(i, j))
    objects.append(i)
Vaughn Cato
  • 63,448
  • 5
  • 82
  • 132
1

Change != to < in your code:

for i in self.objects:
    for j in self.objects:
        if id(i) < id(j) and pygame.sprite.collide_rect(i, j):
            grid.collisions.append(Collision(i, j))
HYRY
  • 94,853
  • 25
  • 187
  • 187