eric.mitchell
eric.mitchell

Reputation: 8845

CGRectIntersectsRect logic inaccuracy

In an app I give random frames to squares every time it is run. I use this logic to make sure

a) each square is not too close to the player
and
b) each square is contained inside the view of the screen c) no square touches any other square

for(UIButton* button in squareArray) {
    BOOL shouldContinue = YES;
    do {
        int randX = arc4random() % 321;
        int randY = arc4random() % 481;
        button.frame = CGRectMake(randX, randY, button.frame.size.width, button.frame.size.height);
        CGRect playerRect = CGRectMake(100, 180, 120, 120);
        for(UIButton* b in squareArray)
            if(!CGRectIntersectsRect(b.frame, button.frame) && 
                !CGRectIntersectsRect(button.frame, playerRect) && 
                CGRectContainsRect(self.view.frame, button.frame)) {
                shouldContinue = NO;
            }
    } while (shouldContinue);
}

With this code, I would expect that each square in squareArray would be (once the loop was complete) completely inside the boundaries of the view, not intersecting with any other of the buttons in the array, and completely outside the boundaries of the playerRect rect, which is a square 120 x 120 in the center of the screen. Is my code wrong? Because I get none of these functionalities.

Edit: I do in fact get one of the desired traits of this method: no square ever intersects playerRect. But I still get squares overlapping each other and squares that are partially out of the view.

Edit 2:

I have made these changes to the nested for loop:

for(UIButton* b in squareArray)
            if(![b isEqual:button]) {
                if(CGRectIntersectsRect(b.frame, button.frame) || 
                   CGRectIntersectsRect(button.frame, playerRect) || 
                   !CGRectContainsRect(CGRectMake(10, 10, 300, 460), button.frame))
                    shouldContinue = YES;
                else
                    shouldContinue = NO;
            }

And now the squares always are within the slightly modified (smaller) rect for the view, and they never intersect the player square. Yay. But they still appear on top of each other. Why?

Upvotes: 1

Views: 902

Answers (3)

eric.mitchell
eric.mitchell

Reputation: 8845

In the end, this worked: (a combination of the wonderful answers of Rob Lourens and rob mayoff- thanks guys, and here, each of you have an upvote! :) )

for(int iii = 0; iii < [squareArray count]; iii++) {

    int randX = arc4random() % 321;
    int randY = arc4random() % 481;

    [(UIButton*)[squareArray objectAtIndex:iii] setFrame:CGRectMake(randX, randY, [(UIButton*)[squareArray objectAtIndex:iii] frame].size.width, [(UIButton*)[squareArray objectAtIndex:iii] frame].size.height)];

    CGRect playerRect = CGRectMake(40, 120, 150, 150);

    for(UIButton* b in squareArray)

        if(![b isEqual:[squareArray objectAtIndex:iii]]) {

            if(CGRectIntersectsRect(b.frame, [(UIButton*)[squareArray objectAtIndex:iii] frame]))
            {
                iii--;
                break;
            } else if(CGRectIntersectsRect([(UIButton*)[squareArray objectAtIndex:iii] frame], playerRect)) {
                iii--;
                break;
            } else if(![self checkBounds:[(UIButton*)[squareArray objectAtIndex:iii] frame]]) {
                iii--;
                break;
            }
        }
}

Upvotes: 0

rob mayoff
rob mayoff

Reputation: 385590

Suppose squareArray contains three buttons A, B, and C with these frames:

A.frame == CGRectMake(10,10,10,10)
B.frame == CGRectMake(10,10,10,10)
C.frame == CGRectMake(20,10,10,10)

Note that A and B overlap, but A and B do not overlap C. Now consider what happens in your inner loop when button == B.

On the first pass through the inner loop, b == A, so you detect CGRectIntersectsRect(b.frame, button.frame) and set shouldContinue = YES.

On the second pass, b == B which means [b isEqual:button], so you do nothing.

On the third (and final) pass, b == C. You find that CGRectIntersectsRect(b.frame, button.frame) is false (because B.frame does not intersect C.frame), and CGRectIntersectsRect(button.frame, playerRect) is false, and !CGRectContainsRect(CGRectMake(10, 10, 300, 460), button.frame) is false. So you set shouldContinue = NO.

Then the inner loop exits. You test shouldContinue, find that it's false, and exit the do/while loop. You have left button B overlapping button A.

Upvotes: 1

Rob Lourens
Rob Lourens

Reputation: 16099

It's not a problem with CGRectIntersectsRect, it's a problem with your logic. When the inner loop finds a single UIButton that does not intersect button, you accept the random coords generated for button. You need to ensure that all the buttons in squareArray don't intersect button, not just one.

Also, what are the frames of the buttons initialized to? Maybe this would be a better solution:

for (int i=0; i<[squareArray count]; i++) {
    UIButton* button = [squareArray objectAtIndex:i];
    BOOL shouldContinue = NO;
    do {
        int randX = arc4random() % 321;
        int randY = arc4random() % 481;
        button.frame = CGRectMake(randX, randY, button.frame.size.width, button.frame.size.height);
        CGRect playerRect = CGRectMake(100, 180, 120, 120);
        for(int j=0; j<i; j++)
            UIButton *b = [squareArray objectAtIndex:j];
            if(CGRectIntersectsRect(b.frame, button.frame) || 
                CGRectIntersectsRect(button.frame, playerRect) || 
                !CGRectContainsRect(self.view.frame, button.frame)) {
                shouldContinue = YES;
            }
    } while (shouldContinue);
}

Note that I haven't tested that at all. Also, depending on the situation, this could loop forever if there is no valid position for a button. There may be a better solution than placing everything completely randomly, depending on your problem.

Upvotes: 1

Related Questions