3

I have a chain of squares represented in pygame. I have some code that lets me rotate parts of the chain, as follows.

#!/usr/bin/python
import pygame

def draw(square):
    (x,y) = square
    pygame.draw.rect(screen, black, (100+x*20,100+y*20,20,20), 1) 

def rotate(chain, index, direction):
    (pivotx, pivoty) = chain[index]
    if (direction == 1):
        newchain = chain[:index]+[(y-pivoty+pivotx, (x-pivotx)+pivoty) for (x,y) in chain[index:]]
    else:
        newchain = chain[:index]+[(y-pivoty+pivotx, -(x-pivotx)+pivoty) for (x,y) in chain[index:]]
    return newchain

pygame.init()

size = [600, 600]
screen = pygame.display.set_mode(size)
white = (255,255,255)
black = (0,0,0)

n = 20
chain = [(i,0) for i in xrange(n)]

screen.fill(white)
for square in chain:
    draw(square)

pygame.display.flip()
raw_input("Press Enter to continue...")
newchain = rotate(chain, 5, 1)
print chain
print newchain
screen.fill(white)
for square in newchain:
    draw(square)

pygame.display.flip()
raw_input("Press Enter to continue...")

screen.fill(white)
newchain = rotate(newchain, 10,0)
for square in newchain:
    draw(square)

pygame.display.flip()
raw_input("Press Enter to continue...")
pygame.quit()

The function rotate takes an index of a square in the chain and rotates the whole chain after that square by 90 degrees, pivoting around the initial square. The problem is that this is meant to mimic a physical toy so it is not allowed to collide with itself. I can check to see if two squares are on top of each other after a rotation but how can I make sure they wouldn't collide temporarily during a rotation?

Simd
  • 19,447
  • 42
  • 136
  • 271
  • It would be a good idea to build a suite of testcases. For each case you should have a `chain`, `index` and `direction`, and the expected result `True`/`False`? or do you need to know where the collision occurs? – John La Rooy Oct 22 '13 at 01:40
  • 1
    What you're trying to achieve does sound like **Continuous Collision Detection**. Some CCD methods may be overkill for your "simple" scenario, but I think you may want to look into it ([here](http://graphics.ewha.ac.kr/CATCH/) is a good example), especially if you plan on adding more complex motions later on. – BenC Oct 24 '13 at 09:47
  • @BenC Having thought about it now I think what I need to do is to define the shape you get by sweeping through 90 degrees with the second half of the body and then do a static intersection test between this arc-like shape and the first half of the body. – Simd Oct 24 '13 at 10:31
  • @gnibbler Just true or false is fine. – Simd Oct 25 '13 at 09:52

6 Answers6

1

It sounds like you already know how to know if they're overlapping once you do the rotation, unless I am misunderstanding. If that's the case, then it would be relatively easy to define a function to answer that question given a potential rotation in the chain by adding a check to the end of your comprehension:

if (direction == 1):
    newchain = chain[:index]+[(y-pivoty+pivotx, (x-pivotx)+pivoty) for (x,y) in chain[index:] if not overlapping(x, y, pivotx, pivoty)]
else:
    newchain = chain[:index]+[(y-pivoty+pivotx, -(x-pivotx)+pivoty) for (x,y) in chain[index:] if not overlapping(x, y, pivotx, pivoty)]

And of course relying on some kind of:

def overlapping(x, y, px, py):
    if (some logic that determins if this is bad):
        raise Exception('Overlapping')
    return True

You would need to do something useful with the exception, but at least this would check each square as you process it, and break out immediately instead of waiting until after the whole rotation to verify that it's good.

Carl
  • 905
  • 5
  • 9
  • 1
    It's not so simple. The rotations are by 90 degrees so to check a collision once the rotation is complete you just need to see if two squares have the same (x,y) index. But in between we don't have that property. – Simd Oct 24 '13 at 10:25
  • I must be misunderstanding. So, it should never be possible for two of the moving squares to overlap after a move, correct? The only possible overlap is between a moving square and a non-moving square (not part of this current rotation). So as you find the new x,y for a square, it should be possible to validate right then whether one of the fixed squares already has those coordinates? – Carl Oct 28 '13 at 16:52
  • A collision can happen at any time in a rotation by 90 degrees. It is easy to detect if there is a collision after the rotation has completed as you just literally look to see if two squares have the same (x,y) coordinates. The problem is detecting collisions that happen during a rotation. – Simd Oct 31 '13 at 12:22
1

I would use the pygame functions to do that.

1. make your surfaces to sprites.
2. rotate them with pygame.transform.rotate.
3. check collision with pygame functions.
4. this function works perfect for me ;).

def collision(sprite, group):
    rectCol = pygame.sprite.spritecollide(sprite, group, False)
    return [s for s in rectCol if pygame.sprite.collide_mask(sprite, s)]

sprite is one of your squares.
group is all the other squares.
the function returns a list with all squares witch collide with your square.

Jan Meisel
  • 259
  • 3
  • 11
  • This looks like a great idea. I can't see in your code how pygame handles collisions that happen during the rotation though. Would you mind adding some more detail for how this would work (maybe just add some use of pygame.transform.rotate to your code)? – Simd Oct 24 '13 at 10:24
0

What you have to do is check a collision between the two quarter-circles that two sides of the rotating rect inscribe. To check collisions between a quarter-circle and a rectangle you can try adapting this code.

Community
  • 1
  • 1
Claudiu
  • 224,032
  • 165
  • 485
  • 680
  • A problem I have is that the chain could be in any shape and then you rotate the second half of it, say, and you have a complicated shape rotating by 90 degrees. Do I have to check each rectangle in the second half separately against every rectangle in the first, fixed, half? This seems quadratic time in the worst case, which is bad. – Simd Oct 15 '13 at 20:00
  • @felix: oh I gotcha, that's a bit more complicated. you end up getting a sort of arc for each rectangle... i think there's no way arond checkin everything against everything else, somehow. maybe you could somehow generate a polygon of the entire second half, how its rotatoin would look, and then do one collision check against that polygon. – Claudiu Oct 15 '13 at 20:04
  • 1
    For unit collisions you can use a quadtree, to rule out the majority of collision checks. Perhaps there's a similar but chain oriented. Otherwise a regular quadtree probably would work. – ninMonkey Oct 15 '13 at 20:13
  • @monkey Can you explain more how you would use a regular quadtree for this problem? The problem is detect collisions that occur during the rotation (maybe half way through one). – Simd Oct 16 '13 at 08:39
  • I strongly recommend to rotate by smaller angles then 90deg For example by 5deg to avoid collision skipping, and after positive collision detection return to previous angular position. The more complex shape the less angle should be .... – Spektre Oct 17 '13 at 22:22
0

The 2 squares will overlap in transit if:

  • one is stationary, the other is moving
  • the center of one starts to the left of the other, and ends up to the right (cross product will be of use here)
  • their distances to the pivot square are within a range (determined by the corners furthest and closest to the pivot square).

Above I gave an idea how to quickly check 2 given squares.

If you sort the squares by their distance to the pivot square, you will not have to check all pairs, only the pairs that are within a distance (thus avoiding O(N^2)).

maniek
  • 7,087
  • 2
  • 20
  • 43
0

One way you can do it is to model the squares after circles and use the relationship

d=sqrt((x2-x1)^2+(y2-y1)^2) (x1,y1), (x2,y2) being the center of the squares.

where d is the minimum distance between their centres. Then you compare it to r1+r2, where r1 and r2 are the radius of the circles residing in the squares. If d is less than r1+r2, reverse their unit velocity vector, or make them rotate the other way.

You can increase the accuracy of the model by testing the vertices of one square, against the diagonals of another square. For example (please take a graph paper to see this), say we have a square A , vertices being [(2,0),(0,2),(2,4),(4,2)], and another (square B) being [(2,2),(5,2),(5,5),(2,5)], now take any one square (we'll take B) and take any one of it's vertices, say, (2,2). Test if the x-coords (2) lies between the x-coordinate of the diagonally aligned vertices of A, say (2,4) and (2,0). Apparently it does! Then we check it against another diagonal, (0,2) and (4,2). It does too! So, square B has collided with square A and the rotation vector or the velocity vector has to be reversed. You may also check with the y-coords.

You'll have to loop through each square to check if they collide with others. However, you don't have to check all squares as you will only need to concern yourself with squares with min distance of d is less than r1+r2 relative to each other, so you will just need one loop to check if their distances are less than the sum of radius, and another loop to check if their vertices has entered the body of the square. I normally do that in my games which stimulate random motion of particles (e.g. brownian motion)

Daniel Tan
  • 29
  • 4
-1

This problem has been generically solved many, many times. The simplest explanation is that you use an increasing level of detail.

For the shapes themselves you must create either bounding boxes or bounding circles which are large enough to contain the outer-most points in the shape. A bounding circle is nice because it is the smallest shape which will always fully cover the object. It also doesn't need to be regenerated after a rotation because it always describes the maximum space possible for your shape.

For a bounding circle the next operation is to measure the distance between each bounding circle and discard the ones that cannot possibly overlap. This is fairly inexpensive. Note too, that you can discard reflections. I.e. if you tested that shape a cannot overlap shape b, don't now test if shape b overlaps shape a.

Once you have the shapes which "may" overlap, you must use a precise algorithm to see if any point in one shape overlaps any point in the other shape. There are a variety of ways to do this. Some of them are geometric (GJK Algorithm) while others use things like z-buffers or pixel masks. For these latter kind you can draw one shape to a test buffer, then start to draw the second shape. If you check the buffer before you plot a pixel and see there is a pixel already there, you know there is an intersection (collision).

Christopher
  • 8,815
  • 2
  • 32
  • 41
  • The complications here are a) the shape being rotated is not convex but b) it is made up of a limited number of squares. GJK for example seems to only apply to convex shapes unless I am reading the wiki page wrong. So what is needed to use a standard collision detection method is to make the shape swept out by the rotated part first I am guessing which also doesn't seem easy to me. – Simd Oct 21 '13 at 19:18
  • @felix: you can use GJK on multiple convex shapes (testing all possible/relevant pairs). It works pretty well with articulated bodies, especially if you rely on temporal/spatial coherence between successive frames. – BenC Oct 24 '13 at 09:39