5

I'm having a hard time figuring out how to determine the intersection of two NSBezierPath closed objects in cocoa. I did some research online and couldn't find the answer so far.

Here is what I have. enter image description here

I need to write some kind of a method that would return true in all of these cases.

What I was thinking so far is to flatten the rectangle by using bezierPathByFlatteningPath and then take each element (as a line segment) using elementAtIndex: associatedPoints: to go through every point in it and checking if the second object (rectangle or ellipse) contains that point (using containsPoint:).

However, I don't know how to go through all the points of a segment...

If anyone has any hint or idea that might help I would really appreciate it!

Eugene Gordin
  • 4,047
  • 3
  • 47
  • 80
  • 2
    If you are only interested in rectangles, then CGRectIsEmpty( CGRectIntersection(r1, r2)) will return YES is they do NOT intersect, NO otherwise. – verec Mar 22 '13 at 19:33
  • Oh sorry...I guess totally forgot to mention I have ellipses too...I'll update my question – Eugene Gordin Mar 22 '13 at 19:59
  • 1
    If you're interested in an approximate solution that will give you false positives (ie: if the answer is NO, then definitely do NOT intersect, if the answer is YES, then they do intersect most of the time but not always), use the eclipse bounding box as a rectangle to compute the intersection with another rectangle/eclipse bounding box ... – verec Mar 22 '13 at 20:53
  • yeah...I need the exact results, I can't rely on approximation here unfortunately...Thank you a lot for your help! – Eugene Gordin Mar 23 '13 at 00:31
  • 2
    Create a bitmap, then for each pair of path you want to know if they intersect or not: paint the bitmap with white, paint path#1 with color A, 50% opacity paint path#2 with color B, 50% opacity scan all the pixels: when you find a color which is neither white, A, or B, you are with a pixel whose color value is the blend of A & B colors at the intersection of path#1 and path#2 Make sure to turn off anti-aliasing/smoothing This is slow, and requires an offscreen bitmap, but should work precisely. – verec Mar 23 '13 at 01:20
  • wow!!! this sounds like a great algorithm! However, I'm using this in my UI...so I need it fast :( Thank you so much for your help! – Eugene Gordin Mar 23 '13 at 20:12
  • My hunch is that unless you have a gazillions path to check, as long as it keeps up with the user touches, it should be "fast enough". The alternative, interpreting the CGPoints that control the shape so as to determine the area which you then want to match against the similarly computed area of some other shape is going to be very computationally involved. Plus the actual painting of each path is actually handled by the GPU on iOS, and only your scanning code has to be on the CPU. You could even think of using the Accelerate framework when Instruments tells you where the bottlenecks are ... – verec Mar 24 '13 at 15:30
  • Also, if you are in the lucky case where your paths only intersect 2 by 2, you could draw all of them at once in the bitmap, and then in your scan code, each time you get a blend color, you could use CGPathContainsPoint to check which of the path contain that point, hence learn which two are intersecting. This wouldn't work if they intersect 3 or more at a time, though ... – verec Mar 24 '13 at 15:37
  • Well, come to think of it ... This should even work for 3 or more! As long as the color you get is neither white, A, or B, then it is an "intersection color". You can probably do a single scan to determine all the path that intersect at once. – verec Mar 24 '13 at 15:45

2 Answers2

3

If you have 2 bezier path rectangles and know each of their frames, then you can use NSIntersectsRect():

NSRect rect1 = NSMakeRect(20.0, 150.0, 300.0, 100.0);
NSRect rect2 = NSMakeRect(100.0, 100.0, 100.0, 200.0);

[[NSColor redColor] set];

[NSBezierPath strokeRect:rect1];
[NSBezierPath strokeRect:rect2];

BOOL intersects = NSIntersectsRect(rect1, rect2);

NSLog(@"intersects == %@", (intersects ? @"YES" : @"NO"));

Produces:

enter image description here

In this case, it would log intersects == YES.

NSGod
  • 22,699
  • 3
  • 58
  • 66
2

Here is a quite fast and clean solution. It also works to test multiple paths vs one, which is fine, isn't it?

It works on CGBezier ( iOS and MacOS compatible )

• 1 - Create the necessaries contexts

Create a 16 bits, one component (no alpha) graphics port with the same size than the view.

  • Don't recreate this context at each test, it's time consuming. Recreate it only when view is resized.
  • Let's call this context computeContext

Create a 16 bits, one component (no alpha) graphics port of 1 pixel width and height.

  • Let's call this context testContext

• 2 - When you need to test the paths intersection:

We work in computeContext for the following operations:

  • Clear the context ( It will be full black )
  • Clip the context to the path you want to test
  • Fill all the paths versus which you want to test with white color
  • ( From now, you don't need to be in the computeContext. ) Get the image and draw it in the testContext with something like :
CGImageRef clippedPathsImage = CGBitmapContextCreateImage(computeContext);
CGRect     onePixSquare = CGRectMake(0,0,1,1);
CGContextDrawImage(testContext, onePixSquare, clippedPathsImage);

( Don't worry, image creation function is fast. It does not malloc any memory since the bits are allocated in the bitmapContext )

We're done!

long*   data = CGBitmapContextGetData(testContext);  
BOOL    intersects = (*data!=0);
  • This is much faster than doing some composition drawing with alpha values

  • This is much faster than testing all the pixels in a big image The image scaling is made internally, hardware accelerated. So it is not a big deal.

If however you want to speed up even more and can afford less precision, you can create a smaller computeContext, for exemple 25% of view size, and render all the paths with a scale transform matrix.

The transfer in the 1 pixel context will then be faster, but you are not sure to detect intersections smaller than 4 pixels in size ( with a 25% scale, Logic ).

Don't use 8 bits gray. I don't think it speeds the process up, and you lose a lot of precision when reducing to 1 pixel. Enough to fail.

Don't forget that the first test to do before using the hard way is to test bounding boxes intersection !

That's all

Opened a GitHub with the library ;) https://github.com/moosefactory

Hope this helps, cheers ! ;)


Here is the code to create a 16bits gray context. It can be more concise, but I declare variables to make it clear. You don't need any bitmapInfo ( last parameter ), since there is no alphaValue, and we don't use float format.

-(CGContextRef)createComputeContext
{
    size_t  w = (size_t)self.bounds.size.width;
    size_t  h = (size_t)self.bounds.size.height;
    size_t  nComps = 1;
    size_t  bits   = 16;
    size_t  bitsPerPix  = bits*nComps;
    size_t  bytesPerRow = bitsPerPix*w;    
    CGColorSpaceRef  cs = CGColorSpaceCreateDeviceGray();

    CGContextRef bmContext = CGBitmapContextCreate(NULL, w, h, bits, bytesPerRow, cs, 0);

    return bmContext;
}
Moose
  • 2,607
  • 24
  • 23
  • This is interesting, but you really shouldn't claim that it's "hardware accelerated" unless you have evidence of that. This kind of CG drawing -- both path rasterization and image drawing into a bitmap context -- generally happens on the CPU. – Kurt Revis Jul 26 '13 at 00:36
  • Hello Kurt, thanks for the precision. I only mentioned GPU acceleration for the image scaling. I know all bitmap operations are handled by the GPU in CIImage and CALayer. I don't think CoreGraphics is different… Maybe it needs a little investigation ;) Cheers. – Moose Aug 31 '13 at 11:07