re: hit detection with NSBezierPath
re: hit detection with NSBezierPath
- Subject: re: hit detection with NSBezierPath
- From: "Michael B. Johnson" <email@hidden>
- Date: Fri, 02 Aug 2002 20:24:15 -0700
<I thought I sent this out yesterday, but my mailer held it for some
reason...>
for those playing at home, here's a recap of what I'm trying to do:
-- the problem -- given a view that has some (potentially large) number
of NSBezierPaths containing some (potentially large) number of line
segments of varying width (put down with a Wacom pen, say) how can you
most quickly determine which particular mark you're over, taking into
account the visible mark (i.e. not just the bound, not just the control
hull, including the corners, width, and end points).
The simple reason I wanted to do this is because I wanted to give a
user immediate feedback when they drag a color swatch over the view;
changing the color of the stroke they're over as they drag over so they
can see what it will look like before they drop. It's also obviously
useful to do picking for selection.
I got a few suggestions, including a generous offer of code to
calculate line intersections. But I decided to go with a more
empirical approach.
- the answer I'm using --
I just finished up coding up an alternate way, which seems to work
great, and is very fast, and was pretty obvious once a few people
convinced me that there wasn't something obvious I was missing in the
AppKit, so I thought I'd share. It's basically the same trick you do
in OpenGL when you want to see what part of a piece of geometry you're
over.
The trick is to assign a unique color to each thing you care about (or
in my case, a group of NSBezierPath objects), render all of them in an
offscreen image, then look at the pixel value and map that pixel value
back to the geometry you care about. This has the advantage that your
hit detection algorithm is using the same wonderful/buggy code that the
user is seeing the result of, so you don't have to get in a fight about
whose line caps are right :-)
A few obvious/not so obvious things:
- Make sure to turn off antialiasing in the image you draw into,
otherwise your pixel value won't map correctly on the edges. It should
also be faster.
- use only opaque colors, so that you get correct values for a single
piece of geometry.
- You also may want to separate your tags out a bit, so you don't get
bit by rounding when going back and forth from ints and floats.
- cache the image once you make it, and make sure to dirty it when you
resize or add new geometry, and recache lazily
- after you make your NSImage that you've drawn into, you may need to
add an NSBitmapImageRepresentation to it so you can get the pixels.
My code looks like this:
NSBitmapImageRep* rep = nil;
NSArray* repArray = [_hitTestImage representations];
int i;
for (i = 0; i < [repArray count]; ++i) {
NSObject *imageRepresentation = [repArray objectAtIndex:i];
if ([imageRepresentation isKindOfClass:[NSBitmapImageRep class]]) {
rep = (NSBitmapImageRep*)imageRepresentation;
}
}
if (!rep) {
NSSize size = [_hitTestImage size];
NSRect rect = NSMakeRect(0, 0, size.width, size.height);
[_hitTestImage lockFocus];
rep = [[[NSBitmapImageRep alloc] initWithFocusedViewRect:rect]
autorelease];
[_hitTestImage unlockFocus];
[_hitTestImage addRepresentation:rep];
}
if (rep) {
// get the pixel value and map back to your geometry
I just tried this on my PowerBook with a 1K x 1K view with a bunch of
strokes in it, and I could very interactively drag a color swatch over
the view and see the underlying strokes change color back and forth as
I moved over them. You get a hit when you enter the view the very
first time as the image gets cached, but after that it's very low cost.
Hope this is useful.
--> Michael B. Johnson, Ph.D. -- email@hidden
--> Studio Tools, Pixar Animation Studios
-->
http://xenia.media.mit.edu/~wave
_______________________________________________
cocoa-dev mailing list | email@hidden
Help/Unsubscribe/Archives:
http://www.lists.apple.com/mailman/listinfo/cocoa-dev
Do not post admin requests to the list. They will be ignored.